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 code works, but it entangles 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”Num treats mathematical operations and conversions as pure, composable steps. It introduces safety
at the type level: operations that can fail (such as parsing an invalid string or dividing by zero)
return a Maybe context, forcing us to handle the failure path explicitly.
flowchart TD
A["Raw Input ('42')"] --> B["Num.parse"]
B --> C["Some(42)"]
C --> D["Num.clamp(0, 100)"]
D --> E["Some(42)"]
B -- "Invalid input" --> F["None"]
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 and
Num.between. Both boundaries are inclusive:
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:
When to use Num vs standard operators
Section titled “When to use Num vs standard operators”Use Num when
Section titled “Use Num when”- You are operating on numbers within a functional pipeline using
pipeor array helpers likeArr.mapandArr.filter. - You are parsing untrusted strings (e.g. query parameters, CSV fields, or form inputs) and want to
avoid boilerplate
isNaNorNaNchecks. - You need safe statistical calculations (
mean,min,max) that gracefully handle empty collections without returningInfinityorNaN. - You want to eliminate inline math boundaries and replace them with clear, readable predicates like
clampandbetween.
Use standard operators when
Section titled “Use standard operators when”- You are writing simple, isolated mathematical formulas (like
x * y + z) inside a standard function body where currying and piping add unnecessary complexity. - You are writing low-level, high-throughput numerical algorithms where the micro-overhead of
allocating
Maybeobjects or curried function wrappers would impact performance.