Num — Number Utilities
Working with numbers in standard JavaScript or TypeScript often leads to code cluttered with
anonymous arrow functions, manual isNaN checks, and verbose range-bounding conditionals.
Simple pipelines — such as transforming API response counts, filtering temperature readings, or
calculating averages from form inputs — frequently require us to write repetitive expressions like
n => n * 100, Math.max(0, Math.min(100, value)), or checking if a parsed number is actually a
number.
Num replaces these imperative checks and inline math formulas with declarative, curried,
pipeline-ready operations. By modeling arithmetic and validation as first-class functions, we can
compose clear, self-describing transformations without scattering temporary arrow functions
throughout our codebase.
The problem with scattered arithmetic and NaN
Section titled “The problem with scattered arithmetic and NaN”Consider a backend service that processes incoming query parameters representing user-configurable slider values:
This function combines parsing, validation, clamping, and fallback logic within a single imperative block. If we want to map this over an array of inputs, we must write a wrapping helper function or embed inline ternary checks.
Additionally, standard JavaScript arithmetic contains structural pitfalls: dividing by zero does not
fail at the type level or throw an exception — it returns Infinity, which silently propagates
through our application, causing unpredictable mathematical errors downstream.
The shift to declarative arithmetic
Section titled “The shift to declarative arithmetic”The Num module provides curried arithmetic, parsing, clamping, and bounds-checking utilities
designed for pipelines.
Safe numeric parsing
Section titled “Safe numeric parsing”To convert a string into a number without encountering the NaN trap, we use Num.parse. It
returns a Maybe<number> context which explicitly models potential parsing failure:
Curried arithmetic
Section titled “Curried arithmetic”Arithmetic functions in Num are curried and place the primary data argument last. This makes them
ideal for composition within pipe and Arr.map:
To prevent runtime errors and silent Infinity propagation, division and remainder operations
return a Maybe context, returning None if the divisor is zero:
Constraining and testing bounds
Section titled “Constraining and testing bounds”We can clamp a number to a specific range or test its membership using Num.clamp, Num.between,
and Num.inRange:
Numeric predicates (Num.is)
Section titled “Numeric predicates (Num.is)”The Num.is drawer provides boolean predicates for inspecting number characteristics within
pipelines:
These predicates compose directly with array helpers like Arr.filter:
Generating sequences
Section titled “Generating sequences”To build sequences of numbers without manual for loops or pre-allocating arrays, we use
Num.range. It generates an array of numbers from start to end (both inclusive) with a
customizable step:
Statistical calculations on collections
Section titled “Statistical calculations on collections”Calculating statistics on arrays of numbers using standard JavaScript arrays can be unsafe. Calling
Math.min or Math.max on an empty array returns Infinity or -Infinity, and dividing a sum by
the length of an empty array returns NaN.
Num provides safe aggregate functions that model empty collections explicitly:
Composing numeric pipelines
Section titled “Composing numeric pipelines”We can combine all of these utilities to build clean, self-contained data flows. Here, we parse raw user inputs, filter out invalid numbers, clamp the valid ones, and calculate their average:
Problems it solves
Section titled “Problems it solves”- Sanitizing and clamping untrusted numeric inputs: When extracting pagination offsets, page
limits, or price filter boundaries from query strings, numbers can fall outside permitted domain
ranges.
Num.parseandNum.clampsanitize and constrain values point-free in pipeline workflows. - Safe statistical computations without
InfinityorNaN: Calling standard functions likeMath.min()on an empty collection returnsInfinity, while dividing by an empty list length yieldsNaN.Num.min,Num.max,Num.mean, andNum.medianreturnMaybe<number>, guaranteeing that empty datasets are handled explicitly. - Division-by-zero protection in progress calculations: Calculating task percentages (e.g.
completed / total) whentotal === 0producesNaNorInfinityin standard JavaScript, causing broken UI progress bars.Num.divideguards against division by zero safely. - Coordinate and bounding box constraints in charts and maps: Clamping cursor offsets, zoom
levels, or viewport coordinates within min/max thresholds (
Num.clamp,Num.inRange) without writing manual ternary chains. - Point-free mathematical pipelines: In shopping cart calculations, currency conversions, and
rate adjustments, combining arithmetic operations (
Num.add,Num.multiply,Num.round) insidepipekeeps mathematical transformations readable without temporary variables.