v0.2.3

A small functional programming library for vanilla JavaScript. Curried functions, algebraic data types, and zero dependencies.

Fully curried Zero dependencies ESM only 496 functions 22 modules
No functions match your search.

Array

Array utilities: constructors, folds, and transformers.

of :: a -> Array a

Lifts a single value into a singleton array — the Applicative `pure`/`return`
for Array. Used by `traverse`, `mapM`, and other Applicative/Monad combinators
whenever a plain value needs to be promoted into the Array context.
Combine with `chain` or `ap` to build larger array computations.

of ('Alice')
// => ['Alice']
of (42)
// => [42]

empty :: () -> Array a

Returns a new empty array — the Monoid identity element for Array.
Because `concat (empty ()) (xs)` and `concat (xs) (empty ())` both equal `xs`,
`empty` is the safe neutral starting point for any accumulation.
Use it wherever an "initial value" of type `Array a` is needed.

empty ()
// => []

range :: Integer -> Integer -> Array Integer

Generates the half-open integer interval `[from, to)` — every integer `n`
satisfying `from <= n < to`. The exclusive upper bound matches Python's
`range` convention and guarantees `range (0) (n).length === n`, making it
easy to produce index arrays. Returns `[]` when `from >= to`.

range (0) (5)
// => [0, 1, 2, 3, 4]
range (2) (6)
// => [2, 3, 4, 5]
range (5) (5)
// => []

unfold :: (b -> Maybe [a, b]) -> b -> Array a

The anamorphism (unfold) for Array — builds a list from a seed by repeatedly
applying `f` until it returns `Nothing`. This is the dual of `reduce`:
where `reduce` collapses a list to a value, `unfold` expands a value into a
list. Use it to generate number sequences, lazy pagination results, countdown
timers, or any corecursive structure without writing an explicit loop.

unfold (n => n <= 0 ? M.nothing () : M.just ([n, n - 1])) (4)
// => [4, 3, 2, 1]
unfold (n => n > 5 ? M.nothing () : M.just ([n * n, n + 1])) (1)
// => [1, 4, 9, 16, 25]

chainRec :: ((a -> Step, b -> Step, a) -> Array Step) -> a -> Array b

Stack-safe recursive array expansion via a trampoline. Unlike a naive
recursive `chain`, `chainRec` never grows the JS call stack, making it
safe for deep graph traversals or large tree enumerations. The step
function receives `next` (enqueue a seed for further expansion) and `done`
(emit a result value), and returns an array of steps so one seed can fan
out into many continuations in a single step.

chainRec ((next, done, x) => x <= 0 ? [done (x)] : [next (x - 1), next (x - 1)]) (2)
// => [0, 0, 0, 0]

equals :: (a -> a -> Boolean) -> Array a -> Array a -> Boolean

Element-wise equality using a curried comparator. Two arrays are equal when
they have the same length and every pair of corresponding elements satisfies
the comparator. The comparator can encode any domain equality — case-
insensitive string comparison, numeric tolerance, or structural equality.

equals (a => b => a === b) (['alice', 'bob']) (['alice', 'bob'])
// => true
equals (a => b => a === b) (['alice', 'bob']) (['alice', 'eve'])
// => false
equals (a => b => a === b) ([1, 2]) ([1, 2, 3])
// => false

lte :: (a -> a -> Boolean) -> Array a -> Array a -> Boolean

Lexicographic less-than-or-equal comparison for arrays using a curried
element comparator. Compares element by element left-to-right; the first
differing position determines the result. A strict prefix is always `lte`
its extension. Useful for sorting multi-key records or checking ordering
without constructing a dedicated comparator.

lte (a => b => a <= b) ([1, 2, 3]) ([1, 2, 4])
// => true
lte (a => b => a <= b) ([1, 3]) ([1, 2])
// => false
lte (a => b => a <= b) ([1, 2]) ([1, 2, 0])
// => true

isOutOfBounds :: Integer -> Array a -> Boolean

Returns `true` when `index` is negative or greater than or equal to the
array's length. Use it as a guard before unsafe index access, or pair it
with `lookup`, which already calls it internally to produce a safe `Maybe`.

isOutOfBounds (3) ([1, 2, 3])
// => true
isOutOfBounds (2) ([1, 2, 3])
// => false
isOutOfBounds (-1) ([1, 2, 3])
// => true

size :: Array a -> Integer

Returns the number of elements in the array. A thin curried wrapper around
`arr.length` that can be passed to higher-order combinators such as `map`
or used in point-free style where a plain property access would not compose.

size (['alice', 'bob', 'carol'])
// => 3
size ([])
// => 0

all :: (a -> Boolean) -> Array a -> Boolean

Returns `true` when every element of the array satisfies the predicate.
Short-circuits on the first failing element. Useful for validating inputs
or checking invariants across a whole collection before processing it.

all (x => x > 0) ([1, 5, 12])
// => true
all (x => x > 0) ([1, -3, 5])
// => false
all (x => typeof x === 'string') (['hello', 'world'])
// => true

any :: (a -> Boolean) -> Array a -> Boolean

Returns `true` when at least one element satisfies the predicate.
Short-circuits as soon as a matching element is found. The existential
counterpart to `all`; together they cover universal and existential
quantification over arrays.

any (x => x > 10) ([3, 7, 15])
// => true
any (x => x < 0) ([1, 2, 3])
// => false

none :: (a -> Boolean) -> Array a -> Boolean

Returns `true` when no element satisfies the predicate. Equivalent to
`!any (pred) (arr)` but reads more naturally in validation contexts, e.g.
"none of the prices are negative". Pair with `all` and `any` to express
rich constraints over collections.

none (x => x < 0) ([3, 7, 12])
// => true
none (x => x > 5) ([1, 3, 7])
// => false

elem :: (a -> a -> Boolean) -> a -> Array a -> Boolean

Tests whether `x` is a member of `arr` using a curried equality function.
Passing a custom comparator makes it easy to check membership by a specific
field, e.g. whether a user name exists in a list. Equivalent to
`any (eq (x)) (arr)`.

elem (a => b => a === b) ('bob') (['alice', 'bob', 'carol'])
// => true
elem (a => b => a === b) ('dave') (['alice', 'bob', 'carol'])
// => false

array :: b -> (a -> Array a -> b) -> Array a -> b

The catamorphism for Array — deconstructs an array into two cases: the
empty array returns `empty_`, and a non-empty array calls `nonEmpty` with
the head and tail. This is the safest way to pattern-match on an array's
shape in a functional pipeline, guaranteeing exhaustive handling of both
cases without unchecked index access.

array ('none') (h => _ => h) (['alice', 'bob', 'carol'])
// => 'alice'
array ('none') (h => _ => h) ([])
// => 'none'
array (0) (h => t => h + t.length) ([10, 20, 30])
// => 12

lookup :: Integer -> Array a -> Maybe a

Safe index access that returns `Just` the element at `index`, or `Nothing`
when the index is out of bounds. Eliminates the risk of returning `undefined`
from an out-of-range access and forces callers to handle the missing case
explicitly. Pair with `M.map` or `M.chain` to transform the result in a
pipeline, or `M.withDefault` to supply a fallback.

lookup (1) (['alice', 'bob', 'carol'])
// => just('bob')
lookup (0) ([99.99])
// => just(99.99)
lookup (5) (['alice', 'bob'])
// => nothing()

head :: Array a -> Maybe a

Safe access to the first element, returning `Just a` for a non-empty array
or `Nothing` for an empty one. Avoids the `undefined` pitfall of `arr[0]`
and forces handling the empty case. In a pipeline, chain the result with
`M.map` to transform it, or use `M.withDefault` to supply a fallback value.

head (['alice', 'bob', 'carol'])
// => just('alice')
head ([42])
// => just(42)
head ([])
// => nothing()

last :: Array a -> Maybe a

Safe access to the last element, returning `Just a` for a non-empty array
or `Nothing` for an empty one. The symmetric counterpart to `head`; both
avoid the `undefined` pitfall of unchecked index access on an empty array.
Use `M.withDefault` to provide a fallback in case the array is empty.

last ([10, 20, 30])
// => just(30)
last (['only'])
// => just('only')
last ([])
// => nothing()

tail :: Array a -> Maybe (Array a)

Safe access to all elements after the first, returning `Just (Array a)` for
a non-empty array or `Nothing` for an empty one. Useful for recursive list
processing where the empty case must be handled explicitly. Pair with `head`
to safely destructure the first element and the rest of the list.

tail ([1, 2, 3])
// => just([2, 3])
tail (['only'])
// => just([])
tail ([])
// => nothing()

init :: Array a -> Maybe (Array a)

Safe access to all elements except the last, returning `Just (Array a)` for
a non-empty array or `Nothing` for an empty one. The symmetric counterpart
to `tail`; together they let you walk a list from either end without ever
producing `undefined`. Use in pipelines that need to strip a trailing element.

init ([1, 2, 3])
// => just([1, 2])
init (['only'])
// => just([])
init ([])
// => nothing()

find :: (a -> Boolean) -> Array a -> Maybe a

Returns `Just` the first element satisfying the predicate, or `Nothing` if
no element matches. Safer than native `Array.prototype.find`, which returns
`undefined` on failure. Pair with `M.map` to transform the found value, or
`M.withDefault` to supply a fallback.

find (u => u.role === 'admin') ([{ name: 'alice', role: 'user' }, { name: 'bob', role: 'admin' }])
// => just({ name: 'bob', role: 'admin' })
find (x => x > 100) ([1, 2, 3])
// => nothing()

findMap :: (a -> Maybe b) -> Array a -> Maybe b

Finds the first element for which `f` returns `Just`, and returns that
wrapped value. Combines a search and a transformation in a single pass,
avoiding redundant iterations. More efficient than mapping and then finding
when the transformation itself is the membership test.

findMap (x => x > 0 ? M.just (x * 10) : M.nothing ()) ([-2, -1, 3, 5])
// => just(30)
findMap (x => x > 100 ? M.just (x) : M.nothing ()) ([1, 2, 3])
// => nothing()

reduce :: (b -> a -> b) -> b -> Array a -> b

Left fold over an array with a curried binary accumulator function.
The foundational building block for most aggregation: summing prices,
merging objects into a map, or building strings. Processes elements
left-to-right; pair with `scanl` when you also need the intermediate
accumulators, not just the final result.

reduce (acc => x => acc + x) (0) ([12.5, 7.0, 3.5])
// => 23
reduce (acc => item => ({ ...acc, [item.id]: item.value })) ({}) ([{ id: 'a', value: 1 }, { id: 'b', value: 2 }])
// => { a: 1, b: 2 }

scanl :: (b -> a -> b) -> b -> Array a -> Array b

Left scan — like `reduce` but returns an array containing the initial value
followed by every intermediate accumulator. Use it for running totals,
progress indicators, or undo stacks where the full history of an
accumulation matters. The output length is always `arr.length + 1`.

scanl (acc => x => acc + x) (0) ([10, 20, 30])
// => [0, 10, 30, 60]
scanl (acc => x => acc * x) (1) ([2, 3, 4])
// => [1, 2, 6, 24]

foldMap :: (b -> b -> b) -> b -> (a -> b) -> Array a -> b

Maps each element to a monoidal value and concatenates them using the
supplied `concat` and `empty` (identity). This is the standard Foldable
`foldMap` operation; choosing different monoids (sum, string, array) gives
different aggregation behaviours through a uniform interface.

foldMap (a => b => a + b) (0) (x => x * 2) ([5, 10, 15])
// => 60
foldMap (a => b => a + b) ('') (s => s.toUpperCase ()) (['hello', ' ', 'world'])
// => 'HELLO WORLD'

joinWith :: String -> Array String -> String

Joins an array of strings into a single string, inserting `sep` between
every adjacent pair. A thin curried wrapper around `Array.prototype.join`
that fits naturally into point-free pipelines. Returns an empty string for
an empty array regardless of the separator.

joinWith (', ') (['alice', 'bob', 'carol'])
// => 'alice, bob, carol'
joinWith ('/') (['home', 'user', 'docs'])
// => 'home/user/docs'
joinWith (',') ([])
// => ''

concat :: Array a -> Array a -> Array a

Concatenates two arrays, returning a new array with elements of `a` followed
by elements of `b`. This is the Semigroup/Monoid `append` operation for
Array; pair it with `empty` to get the full Monoid. Useful for building up
arrays incrementally in a pipeline or `reduce`.

concat (['alice', 'bob']) (['carol', 'dave'])
// => ['alice', 'bob', 'carol', 'dave']
concat ([1, 2]) ([])
// => [1, 2]

append :: a -> Array a -> Array a

Appends a single element to the end of an array, returning a new array
without mutating the original. Use in a `reduce` accumulator when building
up a list one element at a time, or as the curried equivalent of
`arr.concat ([x])`.

append ('dave') (['alice', 'bob', 'carol'])
// => ['alice', 'bob', 'carol', 'dave']
append (0) ([1, 2, 3])
// => [1, 2, 3, 0]

prepend :: a -> Array a -> Array a

Prepends a single element to the front of an array, returning a new array
without mutating the original. The symmetric counterpart to `append`; use it
to cons-up lists in head-first order, or as the stack-push operation in a
purely-functional style.

prepend ('alice') (['bob', 'carol'])
// => ['alice', 'bob', 'carol']
prepend (0) ([1, 2, 3])
// => [0, 1, 2, 3]

map :: (a -> b) -> Array a -> Array b

The Functor instance for Array — applies `f` to every element, returning a
new array of the same length. This is the primary way to transform array
contents while preserving structure. Compose with `filter` and `chain` to
build expressive data pipelines over collections of domain objects.

map (u => u.name) ([{ name: 'alice', age: 30 }, { name: 'bob', age: 25 }])
// => ['alice', 'bob']
map (price => price * 1.2) ([9.99, 19.99, 4.99])
// => [11.988, 23.988, 5.988]

filter :: (a -> Boolean) -> Array a -> Array a

Keeps only the elements that satisfy the predicate, preserving order.
The complement `reject` removes matching elements instead. Use `partition`
when you need both the passing and failing subsets in a single pass over
the array.

filter (u => u.active) ([{ name: 'alice', active: true }, { name: 'bob', active: false }])
// => [{ name: 'alice', active: true }]
filter (x => x > 0) ([-3, 1, -1, 2])
// => [1, 2]

reject :: (a -> Boolean) -> Array a -> Array a

Removes every element that satisfies the predicate, keeping the rest.
The exact complement of `filter`: `reject (pred) (arr)` is equivalent to
`filter (x => !pred (x)) (arr)`. Use `partition` when you need both the
passing and failing subsets simultaneously.

reject (u => u.banned) ([{ name: 'alice', banned: false }, { name: 'mallory', banned: true }])
// => [{ name: 'alice', banned: false }]
reject (x => x % 2 === 0) ([1, 2, 3, 4, 5])
// => [1, 3, 5]

partition :: (a -> Boolean) -> Array a -> [Array a, Array a]

Splits an array into a pair `[passing, failing]` based on a predicate in a
single pass. More efficient than calling `filter` and `reject` separately
when both subsets are needed — for example, separating valid orders from
invalid ones before processing each group differently.

partition (x => x > 0) ([-2, 3, -1, 4, 0])
// => [[3, 4], [-2, -1, 0]]
partition (u => u.role === 'admin') ([{ name: 'alice', role: 'admin' }, { name: 'bob', role: 'user' }])
// => [[{ name: 'alice', role: 'admin' }], [{ name: 'bob', role: 'user' }]]

chain :: (a -> Array b) -> Array a -> Array b

Monadic bind (flatMap) for Array — maps `f` over each element and
concatenates the resulting arrays. Solves the 1-to-many expansion problem:
each input produces zero or more outputs. Return `[]` for unwanted elements
to combine filtering and transformation in a single pass. Chain multiple
`chain` calls to compose multi-step expansions.

chain (tag => ['#' + tag, tag.toUpperCase ()]) (['js', 'fp'])
// => ['#js', 'JS', '#fp', 'FP']
chain (x => x > 0 ? [x] : []) ([-1, 2, -3, 4])
// => [2, 4]

flatten :: Array (Array a) -> Array a

Flattens exactly one level of array nesting. Equivalent to `chain (x => x)`
or `Array.prototype.flat (1)`. Use when `map` has produced an
`Array (Array a)` and you want to collapse it. For deeper nesting, apply
`flatten` multiple times or use `chain` recursively.

flatten ([[1, 2], [3], [4, 5]])
// => [1, 2, 3, 4, 5]
flatten ([['alice', 'bob'], [], ['carol']])
// => ['alice', 'bob', 'carol']

ap :: Array (a -> b) -> Array a -> Array b

Applies an array of functions to an array of values, collecting every
(function, value) pair — the Applicative instance for Array. The result is
the Cartesian product with `fns.length * arr.length` elements. Useful for
generating all combinations of options, e.g. every size × colour variant
of a product.

ap ([x => x + 1, x => x * 10]) ([5, 20])
// => [6, 21, 50, 200]
ap ([s => 'small ' + s, s => 'large ' + s]) (['red', 'blue'])
// => ['small red', 'small blue', 'large red', 'large blue']

reverse :: Array a -> Array a

Returns a new array with elements in reversed order without mutating the
original. Safe to use in pipelines where immutability matters, unlike
`Array.prototype.reverse` which sorts in place.

reverse ([1, 2, 3, 4, 5])
// => [5, 4, 3, 2, 1]
reverse (['alice', 'bob', 'carol'])
// => ['carol', 'bob', 'alice']

sort :: (a -> a -> Boolean) -> Array a -> Array a

Stable sort using a curried `lte` (≤) comparator. Elements with equal keys
preserve their original relative order. Pass a plain `a => b => a <= b` for
natural ordering, or use `toComparator` from `ordering.js` to convert an
`Ordering`-returning comparison function.

sort (a => b => a <= b) ([30, 10, 20])
// => [10, 20, 30]
sort (a => b => a >= b) ([30, 10, 20])
// => [30, 20, 10]

sortBy :: (b -> b -> Boolean) -> (a -> b) -> Array a -> Array a

Stable sort on a projected key using a curried `lte` comparator. Avoids
manually extracting the key inside a comparator; supply the key extractor
as `toKey`. Very practical for sorting arrays of objects by a specific
field, e.g. sorting products by price or users by name.

sortBy (a => b => a <= b) (p => p.price) ([{ name: 'widget', price: 9.99 }, { name: 'gadget', price: 4.99 }])
// => [{ name: 'gadget', price: 4.99 }, { name: 'widget', price: 9.99 }]
sortBy (a => b => a <= b) (u => u.name) ([{ name: 'carol' }, { name: 'alice' }, { name: 'bob' }])
// => [{ name: 'alice' }, { name: 'bob' }, { name: 'carol' }]

nub :: (a -> a -> Boolean) -> Array a -> Array a

Deduplicates an array by removing later occurrences of elements already
seen, keeping the first. Uses a curried equality function so it works for
any type: primitives with `===`, objects compared by a specific field, or
custom domain equality. The order of the remaining elements is preserved.

nub (a => b => a === b) (['alice', 'bob', 'alice', 'carol', 'bob'])
// => ['alice', 'bob', 'carol']
nub (a => b => a.id === b.id) ([{ id: 1, v: 'a' }, { id: 2, v: 'b' }, { id: 1, v: 'c' }])
// => [{ id: 1, v: 'a' }, { id: 2, v: 'b' }]

extend :: (Array a -> b) -> Array a -> Array b

Comonad `extend` for Array — applies `f` to every suffix of the array and
collects the results. The suffix starting at index `i` is `arr.slice (i)`.
Useful for sliding-window computations such as running aggregates or local
context extraction, where each position needs access to its own tail.

extend (xs => xs.length) ([10, 20, 30])
// => [3, 2, 1]
extend (head) ([5, 6, 7])
// => [just(5), just(6), just(7)]

take :: Integer -> Array a -> Maybe (Array a)

Returns `Just` the first `n` elements when `0 <= n <= arr.length`, or
`Nothing` when `n` is out of range. Unlike `Array.prototype.slice`, this
forces you to handle the case where the array is shorter than expected.
Use `M.withDefault ([])` to fall back to an empty array silently.

take (2) (['alice', 'bob', 'carol'])
// => just(['alice', 'bob'])
take (0) ([1, 2, 3])
// => just([])
take (5) ([1, 2, 3])
// => nothing()

drop :: Integer -> Array a -> Maybe (Array a)

Returns `Just` the array after discarding the first `n` elements when
`0 <= n <= arr.length`, or `Nothing` if `n` is out of range. Use this
instead of unchecked slicing when the caller cannot guarantee the array is
long enough. Pair with `take` for safe prefix/suffix splitting.

drop (2) (['alice', 'bob', 'carol'])
// => just(['carol'])
drop (0) ([1, 2, 3])
// => just([1, 2, 3])
drop (5) ([1, 2, 3])
// => nothing()

takeLast :: Integer -> Array a -> Maybe (Array a)

Returns `Just` the last `n` elements when `0 <= n <= arr.length`, or
`Nothing` when `n` is out of range. The symmetric counterpart to `take`;
useful for extracting a trailing window such as the most recent N log
entries from a history array.

takeLast (2) ([10, 20, 30, 40])
// => just([30, 40])
takeLast (0) ([1, 2, 3])
// => just([])
takeLast (5) ([1, 2, 3])
// => nothing()

dropLast :: Integer -> Array a -> Maybe (Array a)

Returns `Just` the array with the last `n` elements removed when
`0 <= n <= arr.length`, or `Nothing` if `n` is out of range. The symmetric
counterpart to `drop`; use it to strip a trailing sentinel or page marker
from a fetched list before further processing.

dropLast (2) ([10, 20, 30, 40])
// => just([10, 20])
dropLast (0) ([1, 2, 3])
// => just([1, 2, 3])
dropLast (5) ([1, 2, 3])
// => nothing()

replicate :: Integer -> a -> Array a

Creates an array of exactly `n` copies of `x`. Useful for initialising a
fixed-size buffer, producing a default value array, or generating test data
without writing an explicit loop. Returns `[]` when `n` is zero or negative.

replicate (3) ('*')
// => ['*', '*', '*']
replicate (4) (0)
// => [0, 0, 0, 0]
replicate (0) ('x')
// => []

span :: (a -> Boolean) -> Array a -> [Array a, Array a]

Splits an array into `[prefix, rest]` where `prefix` is the longest leading
run of elements satisfying the predicate, and `rest` is everything from the
first failing element onward. Use `break_` for the complementary split
(splits at the first passing element), or `splitAt` for an index-based
variant. Both parts together always reconstruct the original array.

span (x => x < 5) ([1, 3, 5, 7])
// => [[1, 3], [5, 7]]
span (x => x < 0) ([1, 2, 3])
// => [[], [1, 2, 3]]
span (x => x > 0) ([1, 2, 3])
// => [[1, 2, 3], []]

break_ :: (a -> Boolean) -> Array a -> [Array a, Array a]

Splits an array at the first element satisfying the predicate, returning
`[prefix, rest]`. Equivalent to `span (complement (pred))`; the prefix runs
until the first element that passes the test. Use `span` when you want to
split on the first failure, and `break_` when you want to split on the first
success.

break_ (x => x > 3) ([1, 2, 4, 5])
// => [[1, 2], [4, 5]]
break_ (x => x > 0) ([1, 2, 3])
// => [[], [1, 2, 3]]
break_ (x => x < 0) ([1, 2, 3])
// => [[1, 2, 3], []]

splitAt :: Integer -> Array a -> [Array a, Array a]

Splits the array at index `n`, returning `[arr.slice (0, n), arr.slice (n)]`.
When `n <= 0` the prefix is `[]`; when `n >= arr.length` the suffix is `[]`.
Unlike `take` and `drop`, `splitAt` never returns `Nothing` — it clamps
silently. Use for fixed-offset partitioning such as header/body separation.

splitAt (2) (['a', 'b', 'c', 'd'])
// => [['a', 'b'], ['c', 'd']]
splitAt (0) ([1, 2, 3])
// => [[], [1, 2, 3]]
splitAt (5) ([1, 2, 3])
// => [[1, 2, 3], []]

until :: (a -> Boolean) -> (a -> a) -> a -> a

Iterates `f` starting from `x`, returning the first value that satisfies
`pred`. Models a while-loop in purely-functional style and is useful for
numeric convergence, binary search seed generation, or repeated
transformation until a stable state. Diverges if `pred` never becomes true.

until (x => x > 100) (x => x * 2) (1)
// => 128
until (x => x <= 1) (x => Math.floor (x / 2)) (100)
// => 0

takeWhile :: (a -> Boolean) -> Array a -> Array a

Returns the longest leading prefix of elements satisfying the predicate.
Unlike `take`, the cutoff is determined by data content rather than a fixed
count. Use `span` when you also need the remainder, or `dropWhile` for the
complement. Returns `[]` if the first element already fails the predicate.

takeWhile (x => x < 5) ([1, 3, 5, 7, 2])
// => [1, 3]
takeWhile (x => x > 0) ([4, 2, -1, 3])
// => [4, 2]
takeWhile (x => x < 0) ([1, 2, 3])
// => []

dropWhile :: (a -> Boolean) -> Array a -> Array a

Drops the longest leading prefix of elements satisfying the predicate and
returns the remainder. The complement of `takeWhile`; together they form
`span`. Useful for skipping over a run of sentinels or headers at the start
of a list before processing the meaningful payload.

dropWhile (x => x < 5) ([1, 3, 5, 7, 2])
// => [5, 7, 2]
dropWhile (x => x > 0) ([4, 2, -1, 3])
// => [-1, 3]
dropWhile (x => x > 0) ([1, 2, 3])
// => []

intercalate :: Array a -> Array (Array a) -> Array a

Inserts a separator array between each sub-array and flattens one level,
producing a single combined array. The array-level analogue of `joinWith`.
Useful for interleaving delimiters between runs of elements, e.g. inserting
divider items between groups. Returns `[]` for an empty outer array.

intercalate ([0]) ([[1, 2], [3, 4], [5]])
// => [1, 2, 0, 3, 4, 0, 5]
intercalate ([',']) ([['a', 'b'], ['c']])
// => ['a', 'b', ',', 'c']

groupBy :: (a -> a -> Boolean) -> Array a -> Array (Array a)

Groups adjacent elements into sub-arrays using a curried equality predicate.
Only consecutive equal elements are grouped; sort the array first if you
want all occurrences of a value merged. Useful for run-length encoding,
collapsing duplicate log entries, or splitting a sorted list into equal
runs before aggregating each group.

groupBy (a => b => a === b) ([1, 1, 2, 2, 2, 3])
// => [[1, 1], [2, 2, 2], [3]]
groupBy (a => b => a.dept === b.dept) ([{ name: 'alice', dept: 'eng' }, { name: 'bob', dept: 'eng' }, { name: 'carol', dept: 'hr' }])
// => [[{ name: 'alice', dept: 'eng' }, { name: 'bob', dept: 'eng' }], [{ name: 'carol', dept: 'hr' }]]

zip :: Array a -> Array b -> Array [a, b]

Pairs elements from two arrays position-by-position, truncating to the
shorter length. Excess elements of the longer array are discarded. Use
`zipWith` to apply a combining function instead of producing raw pairs, or
pass the result to `reduce` to build a lookup map.

zip (['alice', 'bob', 'carol']) ([10, 20])
// => [['alice', 10], ['bob', 20]]
zip ([1, 2, 3]) ([4, 5, 6])
// => [[1, 4], [2, 5], [3, 6]]

zipWith :: (a -> b -> c) -> Array a -> Array b -> Array c

Combines corresponding elements from two arrays using `f`, truncating to
the shorter length. A generalisation of `zip`: where `zip` always produces
pairs, `zipWith` can produce any value, making it useful for vector
arithmetic, parallel transformation, or merging related datasets by position.

zipWith (name => score => ({ name, score })) (['alice', 'bob']) ([95, 87])
// => [{ name: 'alice', score: 95 }, { name: 'bob', score: 87 }]
zipWith (a => b => a + b) ([1, 2, 3]) ([10, 20])
// => [11, 22]

traverse :: (b -> f b) -> (f (a->b) -> f a -> f b) -> ((a->b) -> f a -> f b) -> (a -> f b) -> Array a -> f (Array b)

Applicative traversal — maps an effectful function `f` over an array and
sequences the effects, collecting all results into a single wrapped array.
With Maybe this gives "all-or-nothing" semantics: if any element produces
`Nothing`, the entire traversal short-circuits to `Nothing`. Unlike `map`,
`traverse` propagates and combines the effects of each step. Pass the target
Applicative's `of`, `ap`, and `map` explicitly so this works with any
Applicative (Maybe, Either, Array, Promise, etc.).

apOf :: b -> f b (pure / of)
apAp :: f (a -> b) -> f a -> f b (ap, curried)
apMap :: (a -> b) -> f a -> f b (map, curried)

const apOf  = x  => M.just (x)
const apAp  = ff => fa => M.chain (f => M.map (f) (fa)) (ff)
const apMap = f  => fa => M.map (f) (fa)
traverse (apOf) (apAp) (apMap) (x => x > 0 ? M.just (x) : M.nothing ()) ([1, 2, 3])
// => just([1, 2, 3])
traverse (apOf) (apAp) (apMap) (x => x > 0 ? M.just (x) : M.nothing ()) ([1, -1, 3])
// => nothing()

mapM :: (m b -> (b -> m c) -> m c) -> (Array b -> m (Array b)) -> (a -> m b) -> Array a -> m (Array b)

Maps a monadic function over an array and sequences the effects, collecting
results — the monadic `mapM` from Haskell. Requires the monad's `chain` and
`of` to be passed explicitly so it is monad-agnostic. Use it instead of
`traverse` when you have a monad rather than a general Applicative. If any
step returns a failing monad (e.g. `Nothing`, `Left`), the whole computation
short-circuits.

mapM (chain) (of) (f) (xs)
≡ sequence through xs applying f to each element

mapM (M.chain) (xs => M.just (xs)) (x => x > 0 ? M.just (x) : M.nothing ()) ([5, 10, 15])
// => just([5, 10, 15])
mapM (M.chain) (xs => M.just (xs)) (x => x > 0 ? M.just (x) : M.nothing ()) ([5, -1, 15])
// => nothing()

forM :: (m b -> (b -> m c) -> m c) -> (Array b -> m (Array b)) -> Array a -> (a -> m b) -> m (Array b)

Like `mapM` but with the array and function arguments swapped — useful when
the array is known upfront and you want to express "for each element of xs,
do this effectful thing". The name mirrors Haskell's `forM`. Internally
delegates to `mapM (chain) (of_) (f) (arr)`.

forM (chain) (of) (xs) (f) ≡ mapM (chain) (of) (f) (xs)

forM (M.chain) (xs => M.just (xs)) ([1, 2, 3]) (x => x > 0 ? M.just (x * 2) : M.nothing ())
// => just([2, 4, 6])
forM (M.chain) (xs => M.just (xs)) ([1, -2, 3]) (x => x > 0 ? M.just (x) : M.nothing ())
// => nothing()

Boolean

Boolean type guard and equality.

isBool :: a -> Boolean

Returns `true` only for the boolean primitives `true` and `false`, not for truthy or
falsy non-boolean values. JavaScript's loose type coercion makes it easy to confuse
`0`, `''`, or `null` with `false` — this guard ensures you are working with an actual
boolean. Use it in `filter` to extract booleans from mixed-type arrays, or as a
precondition before calling `equals`.

isBool (true)
// => true
isBool (1)
// => false
isBool (null)
// => false

equals :: Boolean -> Boolean -> Boolean

Returns `true` only when both arguments are booleans with the same value. Unlike
`===`, this first checks that both arguments are actual booleans, so it will not
accidentally return `true` for non-boolean values that compare equal. The curried
form `equals (true)` produces a reusable "is true" predicate for `filter` or
conditional branching.

equals (true) (true)
// => true
equals (true) (false)
// => false
equals (true) (1)
// => false

Date

Date constructors, comparison, and arithmetic.
All operations treat Date as an immutable value and return new Date instances.
Equality and ordering are based on milliseconds since epoch (valueOf()).
All day-boundary operations work in UTC to avoid DST surprises.

now :: () -> Date

Returns a new `Date` representing the current system time. Each call produces a
fresh instance with the current instant, so it is not referentially transparent.
Prefer capturing the result once at the boundary of your program and passing it as
an argument to keep core logic pure and easily testable.

now ()
// => Date (current instant)

today :: () -> Date

Returns a new `Date` set to midnight UTC on the current calendar day, stripping the
time component. Working in UTC avoids the ambiguous or non-existent instants that
local-time midnight can produce during DST transitions, making this safe to use
across any timezone.

today ()
// => Date (today at 00:00:00.000 UTC)

fromMs :: Integer -> Date

Constructs a new `Date` from a Unix timestamp in milliseconds since the epoch
(1970-01-01T00:00:00.000Z). This is the standard way to deserialise a numeric epoch
value — such as one returned by a database or REST API — into a `Date` for further
manipulation or display.

fromMs (0)
// => new Date('1970-01-01T00:00:00.000Z')
fromMs (86400000)
// => new Date('1970-01-02T00:00:00.000Z')

parseDate :: String -> Maybe Date

Parses an ISO 8601 string into a `Date`, returning `Just(date)` on success and
`Nothing` for any string that does not produce a valid date. Unlike `new Date(s)`,
the `Maybe` return type forces callers to handle the invalid-input case rather than
silently propagating an invalid `Date` (whose `valueOf()` is `NaN`) through the
pipeline.

parseDate ('2020-01-01')
// => just(new Date('2020-01-01'))
parseDate ('not a date')
// => nothing()

toMs :: Date -> Integer

Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00.000Z).
This is the canonical way to serialise a `Date` to a number for storage, comparison,
or arithmetic, and is the inverse of `fromMs`.

toMs (new Date (0))
// => 0
toMs (new Date ('1970-01-02T00:00:00.000Z'))
// => 86400000

equals :: Date -> Date -> Boolean

Returns `true` when both dates represent exactly the same instant, compared by
millisecond epoch value. The curried form is useful as a predicate for finding a
specific date in an array, or for deduplicating a sorted date list without
converting each `Date` to a timestamp manually.

equals (new Date (0)) (new Date (0))
// => true
equals (new Date (0)) (new Date (1))
// => false

lte :: Date -> Date -> Boolean

Returns `true` when date `a` is at or before date `b`. The curried form —
`lte (deadline)` — produces a reusable "on or before the deadline" predicate
suitable for `filter` calls, and serves as the base from which `lt`, `gte`, and
`gt` are all derived.

lte (new Date (0)) (new Date (1))
// => true
lte (new Date (1)) (new Date (0))
// => false

lt :: Date -> Date -> Boolean

Returns `true` when date `a` is strictly earlier than date `b`. Use the curried
form to filter an array for dates before a specific cut-off, or to verify that a
start date precedes an end date in a form validation step.

lt (new Date (0)) (new Date (1))
// => true
lt (new Date (1)) (new Date (1))
// => false

gte :: Date -> Date -> Boolean

Returns `true` when date `a` is at or after date `b`. The curried form —
`gte (start)` — produces a reusable "on or after the start date" predicate, useful
for filtering an array of dates to those within an open-ended range.

gte (new Date (1)) (new Date (0))
// => true
gte (new Date (0)) (new Date (1))
// => false

gt :: Date -> Date -> Boolean

Returns `true` when date `a` is strictly later than date `b`. Useful for checking
that a date is after a specific point in time — for example, rejecting past dates in
form validation or filtering for events that have not yet occurred.

gt (new Date (1)) (new Date (0))
// => true
gt (new Date (0)) (new Date (0))
// => false

min :: Date -> Date -> Date

Returns the earlier of two dates. Use it in a `reduce` call to find the earliest
date in an array, or to enforce a lower bound — for example, ensuring a date is no
earlier than a project start date.

min (new Date (0)) (new Date (1))
// => new Date(0)
min (new Date (5)) (new Date (2))
// => new Date(2)

max :: Date -> Date -> Date

Returns the later of two dates. Use it in a `reduce` call to find the most recent
date in an array, or to enforce an upper bound — for example, ensuring a date does
not exceed a deadline.

max (new Date (0)) (new Date (1))
// => new Date(1)
max (new Date (5)) (new Date (2))
// => new Date(5)

clamp :: Date -> Date -> Date -> Date

Constrains a date to a closed interval `[lo, hi]`, returning `lo` if the date is
before it and `hi` if it is after. Useful for keeping user-supplied dates inside a
valid booking window or scheduling range. The three-argument curried form allows
the bounds to be fixed once and applied repeatedly.

clamp (new Date (0)) (new Date (10)) (new Date (15))
// => new Date(10)
clamp (new Date (0)) (new Date (10)) (new Date (5))
// => new Date(5)

addDays :: Integer -> Date -> Date

Returns a new `Date` offset by exactly `n` whole days (positive for future, negative
for past), preserving the time-of-day component. Always returns a new object — the
original `Date` is never mutated. The curried form `addDays (7)` produces a reusable
"advance one week" function for use in `map` or `pipe`.

addDays (1) (new Date ('2020-01-01'))
// => new Date('2020-01-02')
addDays (-1) (new Date ('2020-01-01'))
// => new Date('2019-12-31')

diffDays :: Date -> Date -> Integer

Returns the signed number of whole days between two dates, computed as `b − a` and
truncated toward zero. A positive result means `b` is after `a`; a negative result
means `b` is before `a`. Use this to compute deadlines, durations, or to check
whether two dates are within a given number of days of each other.

diffDays (new Date ('2020-01-01')) (new Date ('2020-01-04'))
// => 3
diffDays (new Date ('2020-01-04')) (new Date ('2020-01-01'))
// => -3

startOfDay :: Date -> Date

Returns a new `Date` set to midnight (00:00:00.000) UTC on the same calendar day as
`d`. Working in UTC avoids the ambiguous or non-existent instants that local-time
midnight can produce during DST transitions. Pair it with `endOfDay` to build an
inclusive range covering an entire day.

startOfDay (new Date ('2020-06-15T14:30:00Z'))
// => new Date('2020-06-15T00:00:00.000Z')
startOfDay (new Date ('2020-06-15T00:00:00.000Z'))
// => new Date('2020-06-15T00:00:00.000Z')

endOfDay :: Date -> Date

Returns a new `Date` set to 23:59:59.999 UTC on the same calendar day as `d`. This
is useful for building inclusive date ranges — pair it with `startOfDay` to create a
range that covers an entire day without accidentally including the first instant of
the following day.

endOfDay (new Date ('2020-06-15T00:00:00Z'))
// => new Date('2020-06-15T23:59:59.999Z')

addHours :: Integer -> Date -> Date

Returns a new `Date` offset by exactly `n` whole hours (positive or negative).
Because it adds milliseconds directly, it crosses DST boundaries correctly without
the ambiguity of local-time arithmetic. Always returns a new object — the original
`Date` is never mutated.

addHours (2) (new Date ('2020-01-01T00:00:00Z'))
// => new Date('2020-01-01T02:00:00.000Z')
addHours (-3) (new Date ('2020-01-01T01:00:00Z'))
// => new Date('2019-12-31T22:00:00.000Z')

addMonths :: Integer -> Date -> Date

Returns a new `Date` with `n` calendar months added, clamping the day to the last
valid day of the target month. This prevents the month overflow that
`new Date(y, m + n, d)` causes when `d` exceeds the length of the target month
(e.g. January 31 + 1 month → February 28/29, not March 2/3).

addMonths (1) (new Date ('2020-01-31'))
// => new Date('2020-02-29') (2020 is a leap year)
addMonths (-1) (new Date ('2020-03-31'))
// => new Date('2020-02-29')

addYears :: Integer -> Date -> Date

Returns a new `Date` with `n` calendar years added, delegating to
`addMonths (n * 12)` so that leap-year day clamping is handled automatically.
February 29 in a leap year becomes February 28 in a non-leap target year.

addYears (1) (new Date ('2020-02-29'))
// => new Date('2021-02-28')
addYears (-1) (new Date ('2021-03-15'))
// => new Date('2020-03-15')

startOfMonth :: Date -> Date

Returns a new `Date` set to midnight UTC on the first day of the month containing
`d`. Useful for grouping events by month or for constructing a monthly range
alongside `endOfMonth`.

startOfMonth (new Date ('2020-06-15'))
// => new Date('2020-06-01T00:00:00.000Z')

endOfMonth :: Date -> Date

Returns a new `Date` set to 23:59:59.999 UTC on the last day of the month containing
`d`. Pair it with `startOfMonth` to build an inclusive monthly interval, for example
when querying records within a given month.

endOfMonth (new Date ('2020-06-15'))
// => new Date('2020-06-30T23:59:59.999Z')
endOfMonth (new Date ('2020-02-01'))
// => new Date('2020-02-29T23:59:59.999Z')

startOfWeek :: Date -> Date

Returns a new `Date` set to midnight UTC on the Monday of the ISO week containing
`d` (ISO 8601 defines Monday as the first day of the week). Useful for weekly
grouping, calendar views, or building a week range alongside `endOfWeek`.

startOfWeek (new Date ('2020-06-17'))
// => new Date('2020-06-15T00:00:00.000Z')  (Wednesday → Monday)
startOfWeek (new Date ('2020-06-15'))
// => new Date('2020-06-15T00:00:00.000Z')  (Monday → Monday)

endOfWeek :: Date -> Date

Returns a new `Date` set to 23:59:59.999 UTC on the Sunday of the ISO week
containing `d` (ISO 8601 defines Sunday as the last day of the week). Pair it with
`startOfWeek` to build a full ISO week interval for scheduling or reporting.

endOfWeek (new Date ('2020-06-17'))
// => new Date('2020-06-21T23:59:59.999Z')  (Wednesday → Sunday)
endOfWeek (new Date ('2020-06-21'))
// => new Date('2020-06-21T23:59:59.999Z')  (Sunday → Sunday)

Either

The Either monad (Left | Right).

left :: a -> Either a b

Wraps a value in the Left constructor, conventionally representing failure or an error.
Unlike `nothing` from Maybe, Left carries information about *why* the computation failed,
making it the right choice whenever the error message, code, or object matters downstream.
Use `right` for the success counterpart.

left ('User not found')
// => { tag: 'left', left: 'User not found' }
left ({ code: 404, message: 'Not found' })
// => { tag: 'left', left: { code: 404, message: 'Not found' } }

right :: b -> Either a b

Wraps a value in the Right constructor, conventionally representing a successful result.
The computation "goes right" when it succeeds — all monadic operations (`map`, `chain`)
operate on this side, letting you describe a happy-path pipeline without manually
checking for errors at each step. Use `left` for the failure counterpart.

right ({ id: 1, name: 'Alice' })
// => { tag: 'right', right: { id: 1, name: 'Alice' } }
right (42)
// => { tag: 'right', right: 42 }

fromNullable :: (() -> a) -> b -> Either a b

Lifts a nullable value into Either: Right when the value is non-null/non-undefined,
Left otherwise. The error is provided as a thunk so the message is only evaluated
when the value is absent — useful when constructing the error string is expensive or
depends on context. Common use-case: wrapping a database lookup that returns `null`
when no record is found.

fromNullable (() => 'User not found') (null)
// => left('User not found')
fromNullable (() => 'User not found') ({ id: 1, name: 'Alice' })
// => right({ id: 1, name: 'Alice' })
fromNullable (() => "Config key 'port' is missing") (undefined)
// => left("Config key 'port' is missing")

tryCatch :: ((...a) -> b) -> (Error -> Array a -> c) -> ...a -> Either c b

Wraps a potentially-throwing function so it returns Either instead of throwing.
On success the result goes into Right; on failure `onError` is called with both
the caught Error and the original arguments array, giving full context for building
a descriptive Left message. This curried form lets you partially apply the function
and error-mapper once to produce a reusable, safe wrapper.

tryCatch (JSON.parse) (e => _ => e.message) ('{"a":1}')
// => right({ a: 1 })
tryCatch (JSON.parse) (e => _ => e.message) ('bad json')
// => left('Unexpected token ...')
const safeParseConfig = tryCatch (JSON.parse) (e => args => `Invalid config "${args[0]}": ${e.message}`)
safeParseConfig ('{bad}')
// => left('Invalid config "{bad}": ...')

fromPredicate :: (a -> Boolean) -> (a -> b) -> a -> Either b a

Constructs an Either from a predicate, turning a boolean test into a typed
success/failure value. When the predicate holds the input is placed in Right;
when it fails `onFalse` is applied to produce a descriptive Left. This is the
idiomatic way to convert a validation rule into a pipeline-compatible step.

fromPredicate (x => x > 0) (x => `${x} must be positive`) (5)
// => right(5)
fromPredicate (x => x > 0) (x => `${x} must be positive`) (-3)
// => left('-3 must be positive')
const validateEmail = fromPredicate (s => s.includes ('@')) (s => `'${s}' is not a valid email`)
validateEmail ('not-an-email')
// => left("'not-an-email' is not a valid email")

isLeft :: a -> Boolean

Returns `true` when the value is a Left.
These tag-based guards are runtime type checks typically used inside array
utilities like `lefts`, `rights`, and `partition` to filter mixed arrays of Either values.

isLeft (left ('error'))
// => true
isLeft (right (1))
// => false

isRight :: a -> Boolean

Returns `true` when the value is a Right.
These tag-based guards are runtime type checks typically used inside array
utilities like `lefts`, `rights`, and `partition` to filter mixed arrays of Either values.

isRight (right (42))
// => true
isRight (left ('error'))
// => false

isEither :: a -> Boolean

Returns `true` when the value is any Either (Left or Right).
Useful as a runtime guard when working with heterogeneous data that may or may
not be wrapped in Either — for example, filtering an array of mixed values before
passing them to Either-aware combinators.

isEither (right (1))
// => true
isEither (left ('e'))
// => true
isEither (42)
// => false

either :: (a -> c) -> (b -> c) -> Either a b -> c

The primary fold/destructor for Either — applies one of two functions depending
on which constructor is present and collapses the result to a plain value.
This is the symmetric counterpart to building Either values: every Either created
with `left`/`right` is ultimately consumed with `either`. Prefer it over manual
tag-checking as it keeps both branches visible and enforced in a single expression.

either (err => `Error: ${err}`) (data => data.name) (right ({ name: 'Alice' }))
// => 'Alice'
either (err => `Error: ${err}`) (data => data.name) (left ('Not found'))
// => 'Error: Not found'
either (() => 0) (xs => xs.length) (right ([1, 2, 3]))
// => 3

fromLeft :: a -> Either a b -> a

Safely extracts the Left value, returning the provided default when given a Right.
Useful when you only care about the error side — for example, providing a fallback
error description before logging or reporting. Pair with `fromRight` to extract both sides.

fromLeft ('unknown error') (left ('connection refused'))
// => 'connection refused'
fromLeft ('unknown error') (right ({ id: 1 }))
// => 'unknown error'

fromRight :: b -> Either a b -> b

Safely extracts the Right value, returning the provided default when given a Left.
Useful at the edge of a pipeline when you need a plain value and have a sensible
fallback for the failure case. Pair with `fromLeft` to handle both sides.

fromRight (0) (right (99))
// => 99
fromRight (0) (left ('NaN'))
// => 0

fromEither :: Either a a -> a

Extracts the inner value regardless of which constructor is present.
Only safe — and meaningful — when both sides share the same type, e.g.
`Either String String`, where you want the string whether the operation succeeded
or failed (such as returning a default message string in both cases). Use `either`
with two separate handlers when the types differ.

fromEither (right ('success message'))
// => 'success message'
fromEither (left ('failure message'))
// => 'failure message'

fold :: (b -> a -> b) -> b -> Either l a -> b

Reduces the Right value with a curried binary function and an initial accumulator,
returning the initial value unchanged for Left. Useful for folding a single Right
result into a running total or collection — for example, accumulating a numeric
result into a sum without unwrapping manually.

fold (acc => x => acc + x) (10) (right (5))
// => 15
fold (acc => x => acc + x) (10) (left ('error'))
// => 10

lefts :: Array (Either a b) -> Array a

Filters an array of Either values to only the Left instances and extracts their
inner values. Useful for collecting all errors from a batch operation — for example,
gathering every failed validation message after processing an array of inputs through
a pipeline where each item returns Either.

lefts ([left ('Not found'), right ({ id: 1 }), left ('Unauthorized')])
// => ['Not found', 'Unauthorized']

rights :: Array (Either a b) -> Array b

Filters an array of Either values to only the Right instances and extracts their
inner values. Useful for collecting all successful results from a batch operation —
for example, keeping only the records that parsed successfully after running each
item through a safe parser that returns Either.

rights ([left ('bad'), right ({ id: 1 }), right ({ id: 2 })])
// => [{ id: 1 }, { id: 2 }]

partition :: Array (Either a b) -> [Array b, Array a]

Splits an array of Either values into a tuple `[successes, failures]`, extracting
the inner values from each side in a single pass. This is the most useful of the
three array utilities because it lets you act on both outcomes simultaneously —
for example, persisting valid records while logging errors, without iterating twice.

partition ([right ({ id: 1 }), left ('invalid'), right ({ id: 2 }), left ('missing')])
// => [[{ id: 1 }, { id: 2 }], ['invalid', 'missing']]
partition ([right ('ok'), left ('err')])
// => [['ok'], ['err']]

encase :: (a -> b) -> a -> Either Error b

Lifts a throwing unary function into a total function that returns Either, placing
the raw `Error` object in Left if an exception is thrown. Unlike `tryCatch`, there
is no custom error-mapping step, making it simpler and the right default choice when
you are happy to work with Error objects directly rather than transformed messages.

encase (JSON.parse) ('{"ok":true}')
// => right({ ok: true })
encase (JSON.parse) ('bad json')
// => left(SyntaxError: Unexpected token ...)
encase (obj => obj.deeply.nested.value) (null)
// => left(TypeError: Cannot read properties of null)

toMaybe :: Either a b -> Maybe b

Converts an Either to a Maybe: Right becomes `just` and Left becomes `nothing`.
Use this when you want to discard the error information and continue in a
Maybe-based pipeline — for example, converting a computation result to an optional
value when the reason for absence is no longer relevant downstream.

toMaybe (right ({ id: 1, name: 'Alice' }))
// => just({ id: 1, name: 'Alice' })
toMaybe (left ('User not found'))
// => nothing()

swap :: Either a b -> Either b a

Swaps Left and Right, turning a failure into a success and vice versa.
This is useful when you want to promote the error to the primary position for
further processing — for example, running `map` or `chain` over error values,
or recovering from a known expected-failure case by treating it as the happy path.

swap (left ('expected error'))
// => right('expected error')
swap (right (42))
// => left(42)

map :: (b -> c) -> Either a b -> Either a c

Applies a function to the Right value, leaving Left untouched.
This is the core of the happy-path abstraction: you describe a chain of
transformations assuming success, and any Left encountered at any step is
automatically propagated through without executing subsequent steps.

map (user => user.name) (right ({ id: 1, name: 'Alice' }))
// => right('Alice')
map (user => user.name) (left ('User not found'))
// => left('User not found')
map (x => x * 2) (map (x => x + 1) (right (4)))
// => right(10)

mapLeft :: (a -> c) -> Either a b -> Either c b

Applies a function to the Left value, leaving Right untouched.
The mirror of `map` for the error side — useful for transforming a raw error
(e.g. an Error object from the runtime) into a user-facing message, or for
normalising errors from different sources into a common error type.

mapLeft (err => `Request failed: ${err.message}`) (left (new Error ('Timeout')))
// => left('Request failed: Timeout')
mapLeft (err => `Request failed: ${err.message}`) (right ({ status: 200 }))
// => right({ status: 200 })

bimap :: (a -> c) -> (b -> d) -> Either a b -> Either c d

Maps both sides simultaneously: Left with the first function, Right with the second.
Use `bimap` when you need to normalise both the error type and the success type in
one step — for example, converting raw strings on both sides into typed domain objects
without chaining separate `map` and `mapLeft` calls.

bimap (err => ({ error: err })) (val => val.toUpperCase ()) (right ('hello'))
// => right('HELLO')
bimap (err => ({ error: err })) (val => val.toUpperCase ()) (left ('not found'))
// => left({ error: 'not found' })

ap :: Either a (b -> c) -> Either a b -> Either a c

Applies a function wrapped in a Right to a value wrapped in a Right.
Enables combining two independent Either computations — for example, running two
separate validations and applying their results to a constructor. Note that `ap`
short-circuits on the first Left it encounters; use a Validation applicative if
you need to accumulate multiple errors rather than stopping at the first failure.

ap (right (x => x * 3)) (right (7))
// => right(21)
ap (left ('invalid fn')) (right (7))
// => left('invalid fn')
ap (right (x => x * 3)) (left ('invalid value'))
// => left('invalid value')

chain :: (b -> Either a c) -> Either a b -> Either a c

Sequences a computation that itself returns an Either, enabling multi-step pipelines
where any step can fail with a Left that short-circuits the rest. This is the heart
of railway-oriented programming: data flows along the success rail through each
`chain` step, and the moment one returns a Left all subsequent steps are bypassed.

chain (n => n > 0 ? right (n) : left ('must be positive')) (right (5))
// => right(5)
chain (n => n > 0 ? right (n) : left ('must be positive')) (right (-1))
// => left('must be positive')
chain (n => n > 0 ? right (n) : left ('must be positive')) (left ('already failed'))
// => left('already failed')

chainLeft :: (a -> Either c b) -> Either a b -> Either c b

Sequences a computation over the Left value, enabling error-recovery and retry logic.
If the Either is a Right it passes through untouched; if it is a Left the provided
function is called with the error value and can either recover by returning a Right
or re-fail with a different Left.

chainLeft (_ => right (0)) (left ('missing'))
// => right(0)
chainLeft (e => left (`Wrapped: ${e}`)) (left ('original error'))
// => left('Wrapped: original error')
chainLeft (_ => right (0)) (right (42))
// => right(42)

chainFirst :: (b -> Either a c) -> Either a b -> Either a b

Runs a side-effecting function on the Right value for its effect only, keeping the
original Right unchanged if the function succeeds. If the function returns a Left
the chain is short-circuited with that Left. This is ideal for "do this side effect
but keep the original value" patterns such as logging, writing an audit record, or
sending a notification without altering the data flowing through the pipeline.

chainFirst (x => right (`logged ${x}`)) (right (42))
// => right(42)
chainFirst (_ => left ('audit failed')) (right ({ name: 'Alice' }))
// => left('audit failed')
chainFirst (_ => left ('audit failed')) (left ('earlier error'))
// => left('earlier error')

alt :: Either a b -> Either a b -> Either a b

Returns the first Right encountered, falling back to the second Either when the
first is a Left. Use this for fallback logic — for example, trying a primary data
source and falling back to a secondary source when the first returns a Left.
Arguments are in "fallback-first" order: `alt(fallback)(primary)`.

alt (right ('cached value')) (left ('fetch failed'))
// => right('cached value')
alt (left ('cache miss')) (right ('live value'))
// => right('live value')
alt (left ('all sources failed')) (left ('primary failed'))
// => left('all sources failed')

traverse :: (b -> f b) -> ((a -> b) -> f a -> f b) -> (a -> f b) -> Either l a -> f (Either l b)

Applicative traversal: runs an effectful function over the Right value and re-wraps
the result in Either within the target applicative, leaving Left unchanged and lifted
into that applicative. Use this to run an effect (e.g. an array expansion, an async
call) only on the success side without manually unwrapping and re-wrapping.

traverse (Array.of) (f => xs => xs.map (f)) (x => [x, x * 10]) (right (5))
// => [right(5), right(50)]
traverse (Array.of) (f => xs => xs.map (f)) (x => [x, x * 10]) (left ('no value'))
// => [left('no value')]

Fn

Function combinators and the Reader monad.

id :: a -> a

Identity — returns its argument unchanged.
Most useful as a no-op placeholder in higher-order functions: pass `id` as
the "do-nothing" branch in `maybe` or `either` when you want to preserve the
wrapped value as-is. It is also the identity morphism in function composition,
satisfying `compose (id) (f) ≡ f ≡ compose (f) (id)`.

id ('hello')
// => 'hello'
id (null)
// => null

always :: a -> b -> a

Returns a constant function that ignores its argument and always returns `a`.
Use it to replace any position in a pipeline with a fixed value — for example
`map (always (0))` zeroes out every element of a collection without writing
an explicit lambda. Equivalent to Haskell's `const` and the K combinator.

always ('default') (undefined)
// => 'default'
[1, 2, 3].map (always (0))
// => [0, 0, 0]
always (true) ('anything')
// => true

thrush :: a -> (a -> b) -> b

Thrush / reverse-application — applies a value to a function rather than
a function to a value. This "value-first" orientation makes it natural for
threading a piece of data through a list of transforms via `reduce`:
`[f, g, h].reduce (thrush, value)` is a lightweight alternative to `pipe`.
It is the T combinator in combinatory logic.

thrush (5) (x => x * x)
// => 25
[x => x + 1, x => x * 3, x => x - 2].reduce (thrush, 4)
// => 13
thrush ('hello') (s => s.toUpperCase ())
// => 'HELLO'

tap :: (a -> *) -> a -> a

Runs a side-effecting function on a value and returns the value unchanged.
This lets you insert logging, debugging, or instrumentation anywhere inside a
`pipe` chain without interrupting or altering the data flow — the chain sees
the exact same value before and after the `tap` step.

tap (x => console.log ('value:', x)) (42)
// => 42
pipe ([
  x => x * 2,
  tap (x => console.log ('after double:', x)),
  x => x + 1,
]) (5)
// => 11

flip :: (a -> b -> c) -> b -> a -> c

Flips the order of the first two arguments of a curried binary function.
This is invaluable when you want to partially apply the *second* argument
of a function: `flip (filter)` lets you fix the predicate first so the
resulting function takes the array — ideal for point-free composition.
It is the C combinator.

flip (a => b => a - b) (10) (30)
// => 20
const startsWith = flip (prefix => s => s.startsWith (prefix))
startsWith ('http') ('https://example.com')
// => true

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

S' combinator ("on both") — maps both arguments through the same unary
function before passing them to a binary function. The canonical use-case is
comparisons that need normalisation before being applied: for example,
`on (compare) (s => s.toLowerCase ())` compares two strings by their
lowercased forms without mutating the originals.

on (a => b => a - b) (s => s.length) ('hi') ('hello')
// => -3
on (a => b => a === b) (s => s.toLowerCase ()) ('Hello') ('hello')
// => true
on (a => b => a > b) (x => x % 10) (13) (27)
// => false

once :: (a -> b) -> a -> b

Calls a function at most once; every subsequent call returns the cached
result of the very first invocation, regardless of the arguments supplied.
Ideal for lazy one-time initialisation: `const getConfig = once (loadConfig)`
ensures setup runs exactly once no matter how many callers invoke it.
Unlike `memoize`, `once` ignores all arguments after the first call.

const greet = once (name => `Hello, ${name}!`)
greet ('Alice')
// => 'Hello, Alice!'
greet ('Bob')
// => 'Hello, Alice!'
const initDB = once (() => ({ connected: true }))
initDB ()
// => { connected: true }

memoize :: (a -> b) -> a -> b

Memoises a unary pure function using a `Map` keyed by argument identity.
Repeated calls with the same argument skip recomputation entirely, which can
turn exponential recursive algorithms (like naïve Fibonacci) into linear ones.
Be aware that cache keys use reference equality, so two structurally identical
objects will produce two separate cache entries.

const fib = memoize (n => n <= 1 ? n : fib (n - 1) + fib (n - 2))
fib (10)
// => 55
const expensiveDouble = memoize (n => n * 2)
expensiveDouble (21)
// => 42

allPass :: (a -> Boolean) -> (a -> Boolean) -> a -> Boolean

Combines two predicates with logical AND, returning a new predicate that is
true only when both hold. Chain multiple `allPass` calls to build composite
validation rules in a readable, point-free style — for instance checking that
a number is both positive and even. Short-circuits on the first failing
predicate via `&&`.

const isPositiveEven = allPass (x => x > 0) (x => x % 2 === 0)
isPositiveEven (4)
// => true
isPositiveEven (-2)
// => false
allPass (s => s.length > 0) (s => s[0] === s[0].toUpperCase ()) ('Hello')
// => true

anyPass :: (a -> Boolean) -> (a -> Boolean) -> a -> Boolean

Combines two predicates with logical OR, returning a new predicate that is
true when at least one holds. Use it to express relaxed alternatives — for
instance accepting values that are either very small or very large. Short-
circuits on the first passing predicate via `||`.

const isExtremeTemp = anyPass (t => t < -20) (t => t > 40)
isExtremeTemp (45)
// => true
isExtremeTemp (20)
// => false
anyPass (s => s === 'admin') (s => s === 'superuser') ('admin')
// => true

pipe :: Array (Any -> Any) -> a -> b

Threads a value left-to-right through an ordered array of functions, where
each function's output becomes the next function's input. The left-to-right
order matches natural reading direction, making pipelines easier to follow
than right-to-left `compose`. Use `pipeWith` when steps may short-circuit
inside an ADT such as Maybe or Either.

pipe ([s => s.trim (), s => s.toUpperCase (), s => `[${s}]`]) ('  hello  ')
// => '[HELLO]'
pipe ([x => x * 2, x => x + 10, Math.sqrt]) (27)
// => 8
pipe ([]) (42)
// => 42

pipeWith :: ((a -> f b) -> f a -> f b) -> Array (a -> f b) -> f a -> f b

Threads a value through a sequence of steps where each step is applied via
a custom "bind" combinator, lifting `pipe` into any monad or applicative.
Pass an ADT's `chain` as the binder to get automatic short-circuiting on
failure (Nothing / Left), or pass `map` for a pure lifting pipeline.
This is the go-to tool for ADT pipelines that would otherwise require manual
unwrapping between steps.


// Maybe pipeline — short-circuits when a step returns Nothing
import * as M from './maybe.js'
pipeWith (M.chain) ([
  x => x > 0   ? M.just (x)     : M.nothing (),
  x => x < 100 ? M.just (x * 2) : M.nothing (),
]) (M.just (7))
// => just(14)

// Either pipeline — carries the error through on Left
import * as E from './either.js'
pipeWith (E.chain) ([
  s => s.length > 0 ? E.right (s.trim ()) : E.left ('empty'),
  s => E.right (s.toUpperCase ()),
]) (E.right ('  hello  '))
// => right('HELLO')

compose :: (b -> c) -> (a -> b) -> a -> c

Right-to-left composition of two unary functions — `f` is applied after `g`.
This mirrors the mathematical convention `(f ∘ g)(x) = f(g(x))` and can read
naturally when naming transforms as noun phrases. For three or more functions
prefer `pipe` with a reversed list to avoid reading the data flow backwards.

compose (x => `$${x.toFixed (2)}`) (x => x * 1.1) (100)
// => '$110.00'
compose (xs => xs.join (', ')) (xs => xs.sort ()) (['banana', 'apple', 'cherry'])
// => 'apple, banana, cherry'

juxt :: Array (a -> b) -> a -> Array b

Applies a single value to each function in an array and collects the results.
Perfect for computing multiple aggregates or projections over the same input
in one pass — for instance deriving min, max, and sum of an array all at once
without iterating multiple times. The output array mirrors the order of `fns`.

juxt ([
  xs => Math.min (...xs),
  xs => Math.max (...xs),
  xs => xs.reduce ((a, b) => a + b, 0),
]) ([3, 1, 4, 1, 5])
// => [1, 5, 14]
juxt ([s => s.length, s => s.toUpperCase (), s => s.split ('')]) ('hi')
// => [2, 'HI', ['h', 'i']]

converge :: (b -> c -> d) -> (a -> b) -> (a -> c) -> a -> d

Converge / fork-merge — fans a value out to two independent functions and
merges their results with a binary function. This "split-apply-combine"
pattern lets you express calculations that share an input in point-free style.
The archetypal example is the average: `converge (div) (sum) (length)` passes
the array to both branches and divides the results.

const average = converge (a => b => a / b)
                          (xs => xs.reduce ((a, b) => a + b, 0))
                          (xs => xs.length)
average ([10, 20, 30])
// => 20
converge (a => b => `${a} (${b} chars)`)
          (s => s.toUpperCase ())
          (s => s.length)
          ('hello')
// => 'HELLO (5 chars)'

uncurry :: (a -> b -> c) -> [a, b] -> c

Converts a curried binary function into one that accepts a 2-element tuple
`[a, b]`. This bridges curried FP-style functions with APIs that naturally
produce pairs — `Object.entries` yields `[key, value]` tuples that can be
directly processed with `uncurry`, removing the need for destructuring.

Object.entries ({ a: 1, b: 2 }).map (uncurry (k => v => `${k}=${v}`))
// => ['a=1', 'b=2']
uncurry (a => b => a + b) ([10, 32])
// => 42

curry :: ([a, b] -> c) -> a -> b -> c

Converts a function that accepts a 2-element tuple into a curried binary
function, letting you partially apply one element of the pair at a time.
It is the inverse of `uncurry` and is useful for making pair-based functions
compatible with point-free pipelines and partial application.

curry (([prefix, s]) => prefix + s) ('Mr. ') ('Smith')
// => 'Mr. Smith'
curry (([a, b]) => Math.pow (a, b)) (2) (10)
// => 1024

map :: (a -> b) -> (e -> a) -> e -> b

Reader Functor — post-composes `f` over a reader, transforming its output
while keeping the environment type unchanged. Note: this is NOT array `map`;
it operates on functions of the shape `(env -> a)`. Use it for dependency
injection: `map (format) (getPort)` returns a new reader that reads the port
from the environment and then formats it, all without running any effects.
`map (f) (g)` is exactly `compose (f) (g)`.

const getPort    = env => env.port
const portString = map (n => `port: ${n}`) (getPort)
portString ({ port: 3000 })
// => 'port: 3000'
map (x => x * 2) (e => e.value) ({ value: 21 })
// => 42

ap :: (e -> a -> b) -> (e -> a) -> e -> b

Reader Applicative — S combinator. Combines two readers that both depend on
the same environment: `ff` reads a function from the environment and `fa`
reads a value; they are then applied together. Use this when two separate
environment-dependent computations must be combined without running the
environment twice.

const getGreeting = env => name => `${env.salutation}, ${name}!`
const getName     = env => env.name
ap (getGreeting) (getName) ({ salutation: 'Hello', name: 'Alice' })
// => 'Hello, Alice!'
ap (e => x => e.base + x) (e => e.offset) ({ base: 100, offset: 42 })
// => 142

of :: a -> e -> a

Reader Applicative — lifts a constant value into the Reader context,
producing a reader that ignores the environment and always returns `a`.
Identical in behaviour to `always`. Useful as the starting point in
applicative-style reader composition.

of (42) ({ anything: true })
// => 42
of ('hello') (null)
// => 'hello'

chain :: (a -> e -> b) -> (e -> a) -> e -> b

Reader Monad bind — threads the environment through both a reader and a
Kleisli function. `fa` reads a value from the environment; `f` receives that
value and returns a new reader, which is then run with the *same* environment.
This lets you sequence environment-dependent computations where later steps
may depend on earlier results while retaining access to the original context.

const getUser = env => env.user
const getRole = user => env => env.roles[user] ?? 'guest'
chain (getRole) (getUser) ({ user: 'alice', roles: { alice: 'admin' } })
// => 'admin'
chain (a => e => a + e.suffix) (e => e.prefix) ({ prefix: 'Hello', suffix: ' World' })
// => 'Hello World'

contramap :: (b -> a) -> (a -> c) -> b -> c

Contravariant map — pre-composes a function over a reader's *input*, adapting
it to accept a different environment type. While `map` transforms the output,
`contramap` transforms the input, letting you reuse a reader built for one
environment shape with a differently shaped environment by supplying an
adapter function.

const getPort       = ({ port }) => port
const portFromJSON  = contramap (s => JSON.parse (s)) (getPort)
portFromJSON ('{"port":8080}')
// => 8080
contramap (s => s.length) (n => n > 3) ('hello')
// => true

promap :: (a -> b) -> (c -> d) -> (b -> c) -> a -> d

Profunctor map — applies `f` to the input (contramapping) and `g` to the
output (mapping) of a reader in one step. Use it to adapt a function at
both ends simultaneously: normalise an incoming value before processing and
format the result on the way out, without touching the core logic.

const process = promap
  (s => s.trim ())
  (s => s.toUpperCase ())
  (s => s.replace (/ /g, '_'))
process ('  hello world  ')
// => 'HELLO_WORLD'
promap (x => x + 1) (x => x * 2) (x => x) (4)
// => 10

extend :: (e -> e -> e) -> ((e -> a) -> b) -> (e -> a) -> e -> b

Reader Comonad extend — widens the environment by accumulating it with
a semigroup-like concat function.

extend (x => y => x + y) (f => f (0)) (e => e * 2) (3)
// => 6

pipeK :: Array (a -> e -> b) -> (e -> a) -> e -> b

Left-to-right Kleisli composition over the Reader monad. Each function in
`fns` has type `a -> (e -> b)`, and steps are chained so the environment is
threaded through automatically. Use this to build multi-step dependency-
injected pipelines where each step can read from the shared environment and
produce a value consumed by the next step.

const double  = n => _env => n * 2
const addBase = n => env  => n + env.base
pipeK ([double, addBase]) (env => env.start) ({ start: 5, base: 100 })
// => 110

chainRec :: ((a -> Step, b -> Step, a) -> e -> Step) -> a -> e -> b

Stack-safe tail-recursive Reader bind (Fantasy Land ChainRec).

chainRec ((next, done, n) => _ => n <= 0 ? done (n) : next (n - 1)) (1000) (null)

handleThrow :: ((...a) -> b) -> (b -> a -> r) -> (Error -> a -> r) -> ...a -> r

Wraps a potentially-throwing function so errors are caught and routed to an
explicit `onThrow` handler instead of propagating up the call stack. Both
`onResult` and `onThrow` receive the original argument list as their second
parameter for context-aware handling. This is the functional equivalent of
a try/catch block and pairs naturally with Either: use `right` as `onResult`
and `left` as `onThrow`.

const safeParseJSON = handleThrow (JSON.parse) (r => _args => r) (_err => _args => null)
safeParseJSON ('{"ok":true}')
// => { ok: true }
safeParseJSON ('not json')
// => null
import * as E from './either.js'
const safeDiv = handleThrow
  (([a, b]) => { if (b === 0) throw new Error ('div by zero'); return a / b })
  (r => _args => E.right (r))
  (e => _args => E.left (e.message))
safeDiv ([10, 2])
// => right(5)

Identity

The Identity functor / monad.
Identity a = { tag: 'identity', value: a }

The simplest possible wrapper — no effect, no structure, no absence.
Useful as the base case for monad transformer stacks and as the canonical
"do nothing" functor when an ADT requires a type constructor argument.

identity :: a -> Identity a

Wraps a value in the `Identity` constructor — the simplest possible functor, adding no
effects or structure. `Identity` is useful as the base case in monad transformer stacks
(e.g., `StateT s Identity` becomes plain `State s`) and as a stand-in when an API requires
a type constructor but you want no-op behaviour.

identity (42)
// => { tag: 'identity', value: 42 }
identity ('hello')
// => { tag: 'identity', value: 'hello' }

isIdentity :: a -> Boolean

Returns `true` when the value is an `Identity` — a runtime membership check.
Use as a guard before calling `Identity`-specific operations in contexts where the
type cannot be statically verified, such as after deserialising data or receiving
values from a generic combinator that may return different wrapper types.

isIdentity (identity (1))
// => true
isIdentity ({ tag: 'identity', value: 1 })
// => true
isIdentity (42)
// => false

extract :: Identity a -> a

Extracts the wrapped value from an `Identity`, discarding the wrapper.
The inverse of the `identity` constructor. Because `Identity` has no effects or
additional structure, extraction is always safe and total — no `Maybe` or default
value is needed.

extract (identity (42))
// => 42
extract (identity ('hello'))
// => 'hello'

equals :: (a -> a -> Boolean) -> Identity a -> Identity a -> Boolean

Tests equality of two `Identity` values by delegating to the given comparator for the
inner values. Because `Identity` has no extra structure, two `Identity` values are equal
precisely when their wrapped values are equal. Pass the comparator for the inner type —
for example `(a => b => a === b)` for primitives.

equals (a => b => a === b) (identity (1)) (identity (1))
// => true
equals (a => b => a === b) (identity (1)) (identity (2))
// => false

lte :: (a -> a -> Boolean) -> Identity a -> Identity a -> Boolean

Tests whether the first `Identity` is less than or equal to the second, delegating to the
given comparator for the wrapped values. As with `equals`, `Identity` adds no ordering of
its own — the inner type's ordering is used directly.

lte (a => b => a <= b) (identity (1)) (identity (2))
// => true
lte (a => b => a <= b) (identity (2)) (identity (1))
// => false

map :: (a -> b) -> Identity a -> Identity b

Applies `f` to the wrapped value and returns a new `Identity` holding the result.
This is the `Functor` instance for `Identity` — trivially `identity(f(fa.value))`.
Because `Identity` has no effects to consider, it satisfies the Functor laws trivially,
making it a useful test case and base case for functor-generic code.

map (x => x * 2) (identity (21))
// => identity(42)
map (s => s.toUpperCase()) (identity ('hello'))
// => identity('HELLO')

of :: a -> Identity a

Lifts a value into `Identity` — an alias for the `identity` constructor.
This is the `Applicative` `pure` / `of` for `Identity`. In transformer stacks it serves
as the trivial "no effect" lift, and in functor-generic code it is the canonical way
to inject a plain value into the `Identity` applicative.

of (42)
// => identity(42)
of ('admin')
// => identity('admin')

ap :: Identity (a -> b) -> Identity a -> Identity b

Applies the function wrapped in `ff` to the value wrapped in `fa`.
This is the `Apply` instance for `Identity` — trivially `identity(ff.value(fa.value))`.
Despite its simplicity, `Identity`'s `ap` satisfies all `Apply` laws and can be used
to verify that functor-generic code is wiring `ap` correctly.

ap (identity (x => x * 2)) (identity (21))
// => identity(42)
ap (identity (s => s.trim())) (identity ('  hello  '))
// => identity('hello')

chain :: (a -> Identity b) -> Identity a -> Identity b

Applies `f` to the wrapped value and returns the resulting `Identity` directly.
This is the `Monad` bind (`>>=`) for `Identity` — trivially just `f(fa.value)`.
`Identity`'s `chain` is used to verify monad-generic code: if `chain` works with
`Identity`, it will work with any monad, since `Identity` introduces no additional
constraints or effects.

chain (x => identity (x * 2)) (identity (21))
// => identity(42)
chain (x => identity (x + '!')) (identity ('hello'))
// => identity('hello!')

fold :: (b -> a -> b) -> b -> Identity a -> b

Reduces the single wrapped value with `f` and initial accumulator `init`.
This is the `Foldable` instance for `Identity` — since there is exactly one element,
it simply applies `f(init)(fa.value)`. Use this when writing functor-generic code
that needs to work uniformly over both `Identity` and richer containers like `Array`.

fold (acc => x => acc + x) (10) (identity (5))
// => 15
fold (acc => x => [...acc, x]) ([]) (identity (42))
// => [42]

traverse :: (b -> f b) -> ((a -> b) -> f a -> f b) -> (a -> f b) -> Identity a -> f (Identity b)

Sequences the `Identity` through an applicative functor — applies `f` to the wrapped value
and re-wraps the result in `identity` inside the outer applicative. Since `Identity` holds
exactly one value, this always produces exactly one effectful result. Accepts explicit
`apOf`/`apMap` to remain functor-agnostic.

apOf :: b -> f b (pure / of)
apMap :: (a -> b) -> f a -> f b (map, curried)

const apOf  = Array.of
const apMap = f => xs => xs.map (f)
traverse (apOf) (apMap) (x => [x, -x]) (identity (5))
// => [identity(5), identity(-5)]

extend :: (Identity a -> b) -> Identity a -> Identity b

Comonad `extend` — applies `f` to the entire `Identity` and wraps the result in a new `Identity`.
This is the trivial `Comonad` instance: `extend(f)(fa)` equals `identity(f(fa))`. Use it when
writing comonad-generic code that processes the "context" around a value — with `Identity` the
context is just the value itself, so `extend` behaves like `map` applied to the whole wrapper.

extend (fa => fa.value * 2) (identity (21))
// => identity(42)
extend (fa => extract (fa) + 1) (identity (10))
// => identity(11)

Logic

Predicate and control-flow utilities.

complement :: (a -> Boolean) -> a -> Boolean

Returns a new predicate that is the logical NOT of the original — the
Boolean complement lifted over a function. Useful for inverting filters and
guards without writing explicit `x => !f(x)` wrappers everywhere. Pairs
naturally with `filter`, `when`, and `unless` in point-free pipelines.

complement (x => x > 0) (-1)
// => true
complement (x => x > 0) (5)
// => false
const isEven = x => x % 2 === 0
filter (complement (isEven)) ([1, 2, 3, 4])
// => [1, 3]

bool :: a -> a -> Boolean -> a

Encodes if-then-else as a curried function value rather than a statement,
making branching logic composable as a first-class pipeline step. The first
argument is the false branch and the second is the true branch. Useful with
`map` and `condDefault` to eliminate inline ternary clutter.

bool ('no') ('yes') (true)
// => 'yes'
bool ('no') ('yes') (false)
// => 'no'
map (bool ('inactive') ('active')) ([true, false, true])
// => ['active', 'inactive', 'active']

ifElse :: (a -> Boolean) -> (a -> b) -> (a -> b) -> a -> b

Routes a value through one of two functions depending on a predicate,
making branching logic explicit and composable as a pipeline step. Both
branches are pure functions, so the conditional introduces no side effects.
A classic use-case is absolute value: identity for the positive branch and
negation for the negative.

ifElse (x => x >= 0) (x => x) (x => -x) (-7)
// => 7
ifElse (x => x >= 0) (x => x) (x => -x) (4)
// => 4
ifElse (s => s.length > 20) (s => s.slice (0, 20) + '…') (s => s) ('Short string')
// => 'Short string'

when :: (a -> Boolean) -> (a -> a) -> a -> a

A specialised `ifElse` where the false branch is the identity function,
meaning the value passes through unchanged when the predicate does not hold.
Ideal for conditional transformations inside pipelines where skipping the
step silently is the desired behaviour — for example, clamping, sanitising,
or truncating a value only when necessary.

when (x => x < 0) (() => 0) (-5)
// => 0
when (x => x < 0) (() => 0) (3)
// => 3
when (s => s.length > 20) (s => s.slice (0, 20)) ('A very long username!!!')
// => 'A very long username'

unless :: (a -> Boolean) -> (a -> a) -> a -> a

The dual of `when` — applies f only when the predicate does NOT hold,
leaving the value untouched when it does. Reads naturally as "unless the
value satisfies the condition, transform it." Use it to enforce
postconditions or handle the error case where the "happy path" is identity.

unless (x => x > 0) (x => -x) (-3)
// => 3
unless (x => x > 0) (x => -x) (5)
// => 5
unless (s => s.length >= 8) (s => s.padEnd (8, '_')) ('hello')
// => 'hello___'

cond :: Array [(a -> Boolean), (a -> b)] -> a -> Maybe b

Multi-branch dispatch — tests each [predicate, handler] pair in order and
returns `just(handler(a))` for the first match, or `nothing()` if none
match. Think of it as a composable switch statement that works inside
pipelines: the Maybe return type makes the "no match" case explicit and
forces callers to handle it rather than silently receiving undefined.

cond ([
  [x => x < 0,  () => 'negative'],
  [x => x === 0, () => 'zero'],
  [x => x > 0,  () => 'positive'],
]) (-3)
// => just('negative')
cond ([
  [x => x < 0,  () => 'negative'],
  [x => x === 0, () => 'zero'],
  [x => x > 0,  () => 'positive'],
]) (0)
// => just('zero')
cond ([
  [x => x < 0, () => 'negative'],
]) (5)
// => nothing()

condE :: Array [(a -> Boolean), (a -> b)] -> a -> Either a b

Like `cond` but uses Either instead of Maybe for the no-match case,
wrapping the original value in `left` when no predicate matches. This
preserves the input for downstream error handling or logging rather than
discarding it as `nothing()` would. Prefer `condE` over `cond` when the
unmatched value carries useful diagnostic information.

condE ([
  [x => x < 0,  () => 'negative'],
  [x => x === 0, () => 'zero'],
  [x => x > 0,  () => 'positive'],
]) (42)
// => right('positive')
condE ([
  [x => x < 0, () => 'negative'],
  [x => x > 0, () => 'positive'],
]) (0)
// => left(0)

condDefault :: (a -> b) -> Array [(a -> Boolean), (a -> b)] -> a -> b

Like `cond` but guarantees a result by accepting an explicit fallback
function for the no-match case, avoiding the need to unwrap Maybe at every
call site. Use it when every possible input must produce an output — such
as mapping HTTP status codes to messages or enum values to labels.

condDefault (() => 'Unknown') ([
  [x => x === 200, () => 'OK'],
  [x => x === 404, () => 'Not Found'],
  [x => x === 500, () => 'Server Error'],
]) (404)
// => 'Not Found'
condDefault (() => 'Unknown') ([
  [x => x === 200, () => 'OK'],
  [x => x === 404, () => 'Not Found'],
]) (301)
// => 'Unknown'

match :: Array [(a -> Maybe b), (b -> c)] -> a -> Maybe c

A pattern-matching variant of `cond` where each arm's "predicate" is a
function that simultaneously tests and extracts — returning `just(value)` to
match or `nothing()` to skip. This is strictly more expressive than `cond`
because the extracted inner value, not the original input, is forwarded to
the handler, enabling safe type narrowing and sub-value capture in a single
step. Returns `nothing()` when no arm matches.

const parsePos = x => x > 0 ? M.just (x)  : M.nothing ()
const parseNeg = x => x < 0 ? M.just (-x) : M.nothing ()
match ([
  [parsePos, x => `positive: ${x}`],
  [parseNeg, x => `negative abs: ${x}`],
]) (5)
// => just('positive: 5')
match ([
  [parsePos, x => `positive: ${x}`],
  [parseNeg, x => `negative abs: ${x}`],
]) (-3)
// => just('negative abs: 3')
match ([
  [parsePos, x => `positive: ${x}`],
]) (0)
// => nothing()

condAll :: Array [(a -> Boolean), (a -> b)] -> a -> Array b

Runs every branch whose predicate matches and collects all results into an
array — unlike `cond`, which stops at the first match. Use it when a single
value can satisfy multiple independent rules simultaneously and all outcomes
are needed, such as tagging a number with all applicable labels or firing
multiple event handlers.

condAll ([
  [x => x > 0,       () => 'positive'],
  [x => x % 2 === 0, () => 'even'],
  [x => x > 100,     () => 'large'],
]) (4)
// => ['positive', 'even']
condAll ([
  [x => x > 0,       () => 'positive'],
  [x => x % 2 === 0, () => 'even'],
]) (7)
// => ['positive']
condAll ([
  [x => x > 0, () => 'positive'],
]) (-1)
// => []

guardWith :: Array (a -> Boolean | a -> Maybe b) -> a -> Maybe b

Threads a value through a pipeline of guard steps, short-circuiting with
`nothing()` on the first failure. Each step is auto-detected as either a
predicate (`a -> Boolean`, fails when false) or an optional transformer
(`a -> Maybe b`, passes the inner value forward or short-circuits on
`nothing()`). The dual-mode design lets you freely mix filtering and
narrowing/mapping steps in one declarative pipeline without manual Maybe
wrapping at every stage.

guardWith ([
  x => x > 0,
  x => x < 100,
]) (42)
// => just(42)
guardWith ([
  x => x > 0,
  x => x < 100,
]) (150)
// => nothing()
guardWith ([
  x => x > 0,
  x => M.just (x * 2),
  x => x < 100,
]) (42)
// => just(84)

Maybe

The Maybe monad (Just | Nothing).

just :: a -> Maybe a

Wraps a value in Just — the "presence" constructor for Maybe.
Use `just` when you have a computed result and want to place it into a
Maybe pipeline. It pairs with `nothing` to form the two possible states of
Maybe; downstream consumers such as `map`, `chain`, and `maybe` handle
both states without any explicit null checks.

just ('alice@example.com')
// => { tag: 'just', value: 'alice@example.com' }
just (42)
// => { tag: 'just', value: 42 }

nothing :: () -> Maybe a

Constructs the empty Maybe — represents the total absence of a value.
Use it to signal that an operation produced no result: a failed dictionary
lookup, a missing config key, an optional user field that was never
filled in. Every Maybe-consuming function handles Nothing gracefully
without throwing, so the absence propagates silently through the pipeline.

nothing ()
// => { tag: 'nothing' }

fromNullable :: a -> Maybe a

The primary bridge between nullable JavaScript and Maybe. Returns
Just(a) for any non-null / non-undefined value and Nothing otherwise,
letting you safely lift ordinary JS values — object fields, API responses,
Map lookups — into a Maybe pipeline without manual null checks. Pair with
`chain` or `map` to keep transforming inside the Maybe context, and with
`fromMaybe` or `maybe` to extract a result at the end.

fromNullable (user.address)
// => just({ street: '1 Main St', city: 'Springfield' })
fromNullable (user.middleName)
// => nothing()
fromNullable (null)
// => nothing()

fromPredicate :: (a -> Boolean) -> a -> Maybe a

Returns Just(a) when the predicate holds and Nothing otherwise, turning a
boolean test into a Maybe-producing gate. Use it for validation at the
entry point of a pipeline — you get a Just only when the value meets your
criteria, and Nothing then propagates silently through every subsequent
`map` and `chain` call without any additional branching.

fromPredicate (x => x > 0) (150)
// => just(150)
fromPredicate (x => x > 0) (-5)
// => nothing()
fromPredicate (s => s.length > 0) ('')
// => nothing()

tryCatch :: (() -> a) -> Maybe a

Safely executes a thunk that might throw, returning Just on success and
Nothing if any exception is caught. Ideal for wrapping operations like
JSON.parse, localStorage access, or third-party calls that may fail at
runtime. Unlike `Either.tryCatch`, this variant discards the error value —
use Either.tryCatch when you need to preserve the exception message for
diagnosis or display to the user.

tryCatch (() => JSON.parse ('{"id":1,"name":"Alice"}'))
// => just({ id: 1, name: 'Alice' })
tryCatch (() => JSON.parse ('<not json>'))
// => nothing()
tryCatch (() => localStorage.getItem ('theme'))
// => just('dark')

isJust :: Maybe a -> Boolean

Returns true when the Maybe carries a value (is a Just).
Primarily useful as a low-level guard at non-FP boundaries, but in most
pipelines it is cleaner to use the `maybe` fold rather than branching on
`isJust` explicitly — the fold enforces handling both cases and composes
more naturally.

isJust (just ('alice@example.com'))
// => true
isJust (nothing ())
// => false

isNothing :: Maybe a -> Boolean

Returns true when the Maybe carries no value (is a Nothing).
Useful as a quick early-exit guard, but prefer the `maybe` fold for
extracting or transforming values in a pipeline — checking `isNothing` and
branching imperatively tends to defeat the purpose of working with Maybe.

isNothing (nothing ())
// => true
isNothing (just ('alice@example.com'))
// => false

maybe :: (() -> b) -> (a -> b) -> Maybe a -> b

The primary destructor for Maybe — performs exhaustive case analysis.
`onNothing` is a lazy thunk called only when the Maybe is Nothing,
avoiding unnecessary work when computing the fallback is expensive.
`onJust` receives the unwrapped value when a Just is present. Prefer
`maybe` over `isJust`/`isNothing` whenever you need to extract a result,
because it enforces handling both cases and composes cleanly in pipelines.

maybe (() => 'Anonymous') (user => user.name) (just ({ name: 'Alice', role: 'admin' }))
// => 'Alice'
maybe (() => 'Anonymous') (user => user.name) (nothing ())
// => 'Anonymous'
maybe (() => 0) (price => price * 1.2) (just (100))
// => 120

fromMaybe :: a -> Maybe a -> a

Extracts the value from a Just or returns the provided eager default.
Because the default is evaluated unconditionally before the call, prefer
`fromMaybe_` when computing the fallback is expensive or has side effects.
A common use is supplying a sensible zero or empty value when an optional
field is missing — for example, defaulting a parsed number to 0.

fromMaybe (0) (just (42))
// => 42
fromMaybe (0) (nothing ())
// => 0
fromMaybe ('guest') (fromNullable (session.username))
// => 'guest'

fromMaybe_ :: (() -> a) -> Maybe a -> a

Extracts the value from a Just or calls the lazy thunk to produce the
default. The thunk is invoked only when the Maybe is Nothing, making this
the right choice when computing the fallback involves real work — reading a
config file, running a database query, or constructing a large object —
that you want to skip entirely when a real value is already present.

fromMaybe_ (() => loadDefaultConfig ()) (just ({ theme: 'dark' }))
// => { theme: 'dark' }
fromMaybe_ (() => 'default-role') (nothing ())
// => 'default-role'

toNull :: Maybe a -> a | null

Converts a Maybe back to a null-based nullable — the standard escape hatch
at the boundary of FP code. Use it when interfacing with libraries or APIs
that expect null instead of a Maybe, such as React controlled-input props,
database query parameters, or third-party SDKs.

toNull (just ('alice@example.com'))
// => 'alice@example.com'
toNull (nothing ())
// => null

toUndefined :: Maybe a -> a | undefined

Converts a Maybe back to an undefined-based nullable — the other common
escape hatch at the boundary of FP code. Use it when interfacing with APIs
that treat undefined as "absent" rather than null, such as optional object
spread properties or certain serialisation libraries.

toUndefined (just ('dark'))
// => 'dark'
toUndefined (nothing ())
// => undefined

toEither :: a -> Maybe b -> Either a b

Converts a Maybe into an Either, promoting Nothing to a Left carrying a
caller-supplied error value and Just to a Right. Use this when you need to
enter the Either world — for example, to attach a diagnostic message after
a lookup that may have found nothing, or to chain Maybe-based lookups with
Either-based validation steps.

toEither ('user not found') (just ({ id: 1, name: 'Alice' }))
// => right({ id: 1, name: 'Alice' })
toEither ('user not found') (nothing ())
// => left('user not found')

map :: (a -> b) -> Maybe a -> Maybe b

Applies f to the value inside Just and returns a new Just; passes Nothing
through unchanged. `map` is the core way to transform a value while
staying inside the Maybe context — it lifts a plain function into
Maybe-land without ever unwrapping. Chaining multiple maps is equivalent
to mapping their composition: `map(f)(map(g)(m))` equals
`map(x => f(g(x)))(m)`.

map (user => user.name) (just ({ name: 'Alice', role: 'admin' }))
// => just('Alice')
map (name => name.toUpperCase ()) (just ('alice'))
// => just('ALICE')
map (x => x * 2) (nothing ())
// => nothing()

filter :: (a -> Boolean) -> Maybe a -> Maybe a

Narrows a Just down to Nothing when the value fails the predicate, while
passing an existing Nothing through unchanged. Use it to enforce domain
constraints inside a pipeline — for example, discarding a user who does
not meet an age requirement, or a price that is non-positive — without
breaking the Maybe chain or adding explicit branching.

filter (user => user.age >= 18) (just ({ name: 'Alice', age: 30 }))
// => just({ name: 'Alice', age: 30 })
filter (user => user.age >= 18) (just ({ name: 'Bob', age: 15 }))
// => nothing()
filter (x => x > 0) (nothing ())
// => nothing()

ap :: Maybe (a -> b) -> Maybe a -> Maybe b

Applies a Just-wrapped function to a Just-wrapped value; returns Nothing
if either argument is Nothing. Use `ap` to combine two independent Maybes
— for example, validating two separate optional fields and merging their
results — where neither value depends on the other. When the second Maybe
must be derived from the first value, use `chain` instead.

ap (just (name => name.toUpperCase ())) (just ('alice'))
// => just('ALICE')
ap (nothing ()) (just ('alice'))
// => nothing()
ap (just (x => x * 2)) (nothing ())
// => nothing()

chain :: (a -> Maybe b) -> Maybe a -> Maybe b

Monadic bind — applies f to the value inside Just and returns the result
directly, without double-wrapping; passes Nothing through. Use `chain`
when each step in a pipeline might itself fail and return Nothing — for
example, looking up a user, then their address, then their city — so that
the first Nothing short-circuits all remaining steps automatically.

chain (user => fromNullable (user.address)) (just ({ name: 'Alice', address: '1 Main St' }))
// => just('1 Main St')
chain (user => fromNullable (user.address)) (just ({ name: 'Bob' }))
// => nothing()
chain (user => fromNullable (user.address)) (nothing ())
// => nothing()

chainNullable :: (a -> b | null | undefined) -> Maybe a -> Maybe b

Sequences a Maybe with a nullable-returning function, automatically
wrapping the result via `fromNullable`. Equivalent to
`chain(compose(fromNullable)(f))` but more concise. Use it when you have
accessor functions that return null or undefined — such as direct property
reads or Map.get calls — and want to keep the pipeline flat without
manually calling `fromNullable` at every step.

chainNullable (user => user.address) (just ({ name: 'Alice', address: '1 Main St' }))
// => just('1 Main St')
chainNullable (user => user.address) (just ({ name: 'Bob' }))
// => nothing()
chainNullable (user => user.address) (nothing ())
// => nothing()

alt :: Maybe a -> Maybe a -> Maybe a

Returns the first Just, falling back to the second Maybe if the first is
Nothing. Models the "try primary, fall back to secondary" pattern — for
example, preferring a user-supplied locale over a system default, or using
a cached value before falling back to a freshly computed one. Note that the
fallback is passed as the first (curried) argument due to the fixed
currying order.

alt (just ('en-US')) (just ('fr-FR'))
// => just('fr-FR')
alt (just ('en-US')) (nothing ())
// => just('en-US')
alt (nothing ()) (nothing ())
// => nothing()

fold :: (b -> a -> b) -> b -> Maybe a -> b

Folds a Maybe into a single value — returns `init` for Nothing and applies
`f(init)(value)` for Just. This is the standard Foldable interface; it
lets you reduce a Maybe into any type, such as appending an optional tag
onto a list, summing an optional price into a running total, or converting
a Maybe into a string for display.

fold (tags => tag => [...tags, tag]) ([]) (just ('admin'))
// => ['admin']
fold (tags => tag => [...tags, tag]) ([]) (nothing ())
// => []
fold (total => price => total + price) (0) (just (49.99))
// => 49.99

traverse :: (b -> f b) -> ((a -> b) -> f a -> f b) -> (a -> f b) -> Maybe a -> f (Maybe b)

Sequences a Maybe through an applicative functor — runs `f` which
produces a wrapped value and collects the result into a wrapped Maybe.
For Just it applies `f` to the inner value and maps `just` over the
resulting applicative; for Nothing it lifts `nothing()` into the functor.
Pass `apOf` (pure / of) and `apMap` (fmap) for the target applicative to
keep this function functor-agnostic.

traverse (Array.of) (f => xs => xs.map (f)) (x => [x, -x]) (just (3))
// => [just(3), just(-3)]
traverse (Array.of) (f => xs => xs.map (f)) (x => [x, -x]) (nothing ())
// => [nothing()]

zip :: Maybe a -> Maybe b -> Maybe [a, b]

Combines two independent Maybes into a Just of a pair, returning Nothing
if either is Nothing. Use it when you need both optional values to be
present before proceeding — for example, pairing a user's first and last
name, or combining a latitude and longitude into a coordinate — so that
the pair is only formed when all pieces exist.

zip (just ('Alice')) (just ('Smith'))
// => just(['Alice', 'Smith'])
zip (just (51.509865)) (nothing ())
// => nothing()
zip (nothing ()) (nothing ())
// => nothing()

justs :: Array (Maybe a) -> Array a

Extracts and collects the values from all Just elements in an array,
silently discarding any Nothings. Useful for gathering successful results
after mapping a fallible operation — for example, collecting valid records
from a batch of lookups where some IDs were not found.

justs ([just ('alice'), nothing (), just ('bob'), nothing ()])
// => ['alice', 'bob']
justs ([nothing (), nothing ()])
// => []

mapMaybe :: (a -> Maybe b) -> Array a -> Array b

Maps a Maybe-returning function over an array and collects only the Just
results, performing transformation and filtering in a single pass.
Equivalent to `justs(xs.map(f))` but expressed as a reusable combinator.
Use it whenever you want to simultaneously transform and discard — for
example, parsing strings into numbers and silently dropping the ones that
fail to parse.

mapMaybe (x => x > 0 ? just (x * 10) : nothing ()) ([3, -1, 5, -2])
// => [30, 50]
mapMaybe (s => s.trim ().length > 0 ? just (s.trim ()) : nothing ()) (['  hi  ', '', '  world  '])
// => ['hi', 'world']

Nil

Nullable value utilities (null | undefined treated as "absent").
Nil a = a | null | undefined

empty :: () -> null

Returns `null`, the canonical representation of an absent `Nil` value. Having a
named constructor makes it explicit in code that `null` is being used intentionally
as an empty or absent value, rather than being an accidental omission or
uninitialised variable.

empty ()
// => null

of :: a -> Nil a

Lifts a value into the `Nil` context, normalising `undefined` to `null`. This
ensures that functions which may return either `null` or `undefined` both produce a
consistent `null`, simplifying downstream checks that only need to guard against
one absent value.

of (1)
// => 1
of (null)
// => null
of (undefined)
// => null

isNil :: a -> Boolean

Returns `true` for both `null` and `undefined`, treating them as interchangeable
absent values. JavaScript's dual-nil design often forces you to check both; this
predicate eliminates the repetition. Pass it directly to `filter` to remove absent
values from an array, or use it as a guard before accessing object properties.

isNil (null)
// => true
isNil (undefined)
// => true
isNil (0)
// => false

isNotNil :: a -> Boolean

Returns `true` for any value that is not `null` or `undefined`. This is the
complement of `isNil` and is the most common predicate for filtering out absent
values in a concise, pipeline-friendly way — for example, `array.filter (isNotNil)`
removes all nil entries without writing a lambda.

isNotNil (0)
// => true
isNotNil (null)
// => false

fromPredicate :: (a -> Boolean) -> a -> Nil a

Returns the value if it is non-nil and the predicate returns `true`, otherwise
returns `null`. This is a compact way to combine an existence check with a domain
check into a single step, avoiding nested `if` statements in transformation
pipelines. Already-nil values always return `null` regardless of the predicate.

fromPredicate (x => x > 0) (5)
// => 5
fromPredicate (x => x > 0) (-1)
// => null

fromMaybe :: Maybe a -> Nil a

Converts a `Maybe` value to a `Nil` value by extracting the inner value from `Just`
or returning `null` for `Nothing`. Useful when bridging code that uses `Maybe` for
optional values with code that uses `null` — for example, when writing to a database
or API that expects `null` for absent fields.

fromMaybe (M.just (1))
// => 1
fromMaybe (M.nothing ())
// => null

map :: (a -> b) -> Nil a -> Nil b

Applies a transformation function to the value if it is non-nil, otherwise
propagates `null`. This mirrors the `Maybe` functor's `map` and makes it safe to
chain transformations over potentially-absent values without sprinkling explicit
null checks throughout your code.

map (x => x + 1) (5)
// => 6
map (x => x + 1) (null)
// => null

chain :: (a -> Nil b) -> Nil a -> Nil b

Applies a function that may itself return `null` to the value if it is non-nil,
flattening one level of nullability. Use this instead of `map` when the
transformation function already returns a `Nil` value — otherwise `map` would
deliver a `null` result as a valid (non-null) value, defeating nil-propagation.

chain (x => x > 0 ? x * 2 : null) (5)
// => 10
chain (x => x > 0 ? x * 2 : null) (null)
// => null

getOrElse :: a -> Nil a -> a

Returns the value if non-nil, or the provided default value otherwise. This is the
standard way to terminate a `Nil` pipeline, turning an optional value into a
required one with a sensible fallback. Use `getOrElse_` instead if computing the
default is expensive and should be deferred.

getOrElse (0) (5)
// => 5
getOrElse (0) (null)
// => 0

getOrElse_ :: (() -> a) -> Nil a -> a

Like `getOrElse`, but accepts a zero-argument function (thunk) for the default,
deferring its evaluation until the value is actually absent. This avoids computing
an expensive default — such as a database query result or a complex object — when
the primary value is already present.

getOrElse_ (() => 0) (null)
// => 0
getOrElse_ (() => 42) (7)
// => 7

alt :: Nil a -> Nil a -> Nil a

Returns the first non-nil value, or `null` if both are nil. This implements an
OR-like fallback: if the primary value (second argument) is absent, the secondary
value (first argument) is used instead. Chain multiple `alt` calls to implement a
priority list of optional sources.

alt (5) (null)
// => 5
alt (null) (3)
// => 3
alt (null) (null)
// => null

toMaybe :: Nil a -> Maybe a

Converts a `Nil` value to a `Maybe` — `Just(a)` for a non-nil value and `Nothing`
for `null` or `undefined`. Use this to move from the `Nil` world into the `Maybe`
world when you need `Maybe`'s richer set of combinators (`chain`, `map`, `ap`, etc.)
for further composition.

toMaybe (5)
// => just(5)
toMaybe (null)
// => nothing()

Nonempty

NonEmptyArray: an array guaranteed to have at least one element.
NonEmptyArray a = { tag: 'nonempty', head: a, tail: Array a }

Unlike Array, NonEmptyArray is a Semigroup but not a Monoid (no empty),
and head / last always return a value — never Maybe.

of :: a -> Array a -> NonEmptyArray a

Constructs a `NonEmptyArray` from an explicit head element and a (possibly empty) tail array.
This is the primitive constructor — all other constructors delegate to it. The separation of
`head` and `tail` makes the non-emptiness guarantee structurally explicit: the type cannot
be constructed without providing at least one element.

of (1) ([2, 3])
// => { tag: 'nonempty', head: 1, tail: [2, 3] }
of ('alice') ([])
// => { tag: 'nonempty', head: 'alice', tail: [] }

singleton :: a -> NonEmptyArray a

Constructs the minimal `NonEmptyArray` — a single element with an empty tail.
Use this when you have a guaranteed value and want to bring it into the `NonEmptyArray`
world to compose with other non-empty arrays via `concat`, `map`, or `chain`.

singleton (42)
// => { tag: 'nonempty', head: 42, tail: [] }
singleton ('admin')
// => { tag: 'nonempty', head: 'admin', tail: [] }

fromArray :: Array a -> Maybe (NonEmptyArray a)

Safely converts a plain array to a `NonEmptyArray`, returning `Just(NonEmptyArray)` when
the array has at least one element and `Nothing` when it is empty. This is the boundary
function that bridges the unguaranteed world of plain arrays and the guaranteed world of
`NonEmptyArray` — it forces explicit handling of the empty case exactly once, at the entry point.

fromArray ([1, 2, 3])
// => just(of(1)([2, 3]))
fromArray (['only'])
// => just(singleton('only'))
fromArray ([])
// => nothing()

isNonEmpty :: a -> Boolean

Returns `true` when the value is a `NonEmptyArray` — a runtime membership check.
Note that a plain JavaScript array is not a `NonEmptyArray` even if it is non-empty;
use `fromArray` to convert and validate in one step, or this guard after an explicit
`of` / `singleton` construction.

isNonEmpty (singleton (1))
// => true
isNonEmpty (of (1) ([2, 3]))
// => true
isNonEmpty ([1, 2])
// => false

head :: NonEmptyArray a -> a

Extracts the first element of the `NonEmptyArray` — always safe, never wrapped in `Maybe`.
Because the type guarantees at least one element, `head` returns a plain value without any
wrapping. This eliminates the boilerplate of `fromMaybe` or `getOrElse` calls that would
be required when calling `head` on a plain array.

head (of (1) ([2, 3]))
// => 1
head (singleton ('admin'))
// => 'admin'

tail :: NonEmptyArray a -> Array a

Returns all elements after the first as a plain array — which may be empty.
The result is a plain `Array`, not a `NonEmptyArray`, because there is no guarantee
that the original had more than one element. Use `fromArray` on the result if you
need to continue in the `NonEmptyArray` world.

tail (of (1) ([2, 3]))
// => [2, 3]
tail (singleton (1))
// => []

last :: NonEmptyArray a -> a

Extracts the last element of the `NonEmptyArray` — always safe, never wrapped in `Maybe`.
Like `head`, the non-emptiness guarantee makes this unconditionally safe. Use it instead
of `arr[arr.length - 1]` — which is `undefined` for empty arrays — when you know the
array is non-empty and want to make that invariant explicit in the type.

last (of (1) ([2, 3]))
// => 3
last (singleton (7))
// => 7

init :: NonEmptyArray a -> Array a

Returns all elements except the last as a plain array — which may be empty.
The symmetric counterpart to `last`, mirroring the relationship between `head` and `tail`.
The result is a plain `Array` because a single-element `NonEmptyArray` has an empty `init`,
so the non-emptiness guarantee cannot be preserved.

init (of (1) ([2, 3]))
// => [1, 2]
init (singleton (1))
// => []

toArray :: NonEmptyArray a -> Array a

Converts a `NonEmptyArray` to a plain JavaScript array by prepending the `head` to the `tail`.
Use this at the boundary where `NonEmptyArray`-specific combinators hand off to standard array
operations, or when interfacing with APIs that expect a plain `Array`. The resulting array is
always non-empty, but that fact is no longer tracked in the type.

toArray (of (1) ([2, 3]))
// => [1, 2, 3]
toArray (singleton ('only'))
// => ['only']

size :: NonEmptyArray a -> Integer

Returns the total number of elements — always at least `1`.
Because `NonEmptyArray` is always non-empty, `size` is guaranteed to return a positive
integer. Use it instead of `toArray(nea).length` to avoid the allocation of an intermediate array.

size (of (1) ([2, 3]))
// => 3
size (singleton (42))
// => 1

fold :: (a -> Array a -> b) -> NonEmptyArray a -> b

The canonical eliminator for `NonEmptyArray` — passes `head` and `tail` to a curried function.
Use this for custom destructuring logic that needs to treat the first element specially,
such as using the head as the initial accumulator in a reduce-style operation without
requiring a separate initial value.

fold (h => t => [h, ...t].join (', ')) (of (1) ([2, 3]))
// => '1, 2, 3'
fold (h => t => h + t.length) (of (10) ([20, 30]))
// => 12

equals :: (a -> a -> Boolean) -> NonEmptyArray a -> NonEmptyArray a -> Boolean

Tests element-wise equality between two `NonEmptyArray` values using the given comparator.
Two `NonEmptyArray` values are equal when they contain the same number of elements and each
corresponding pair satisfies `eq`. Converting to plain arrays internally means size difference
is detected early without comparing elements unnecessarily.

equals (a => b => a === b) (of (1) ([2, 3])) (of (1) ([2, 3]))
// => true
equals (a => b => a === b) (of (1) ([2])) (of (1) ([3]))
// => false

concat :: NonEmptyArray a -> NonEmptyArray a -> NonEmptyArray a

Concatenates two `NonEmptyArrays` into a single `NonEmptyArray` — the `Semigroup` instance.
Because both operands are guaranteed non-empty, the result is always non-empty too, so unlike
a plain array `concat` no `Maybe` wrapper is required. Note that `NonEmptyArray` has no
`Monoid` instance: there is no empty element.

concat (of (1) ([2])) (of (3) ([4]))
// => of(1)([2, 3, 4])
concat (singleton ('a')) (of ('b') (['c']))
// => of('a')(['b', 'c'])

map :: (a -> b) -> NonEmptyArray a -> NonEmptyArray b

Applies `f` to every element, preserving the `NonEmptyArray` structure.
Because mapping cannot remove elements, the result is still guaranteed non-empty — no `Maybe`
is required on the return type. Use this instead of `toArray(nea).map(f)` to avoid
converting to a plain array and back.

map (x => x * 2) (of (1) ([2, 3]))
// => of(2)([4, 6])
map (s => s.toUpperCase()) (of ('alice') (['bob']))
// => of('ALICE')(['BOB'])

ap :: NonEmptyArray (a -> b) -> NonEmptyArray a -> NonEmptyArray b

Applies each function in `nef` to each value in `nea`, producing all combinations.
Because both inputs are non-empty, the result is guaranteed non-empty — the Cartesian
product of two non-empty sets is always non-empty. The order is: all values produced
by the first function, then all values produced by the second function, and so on.

ap (of (x => x + 1) ([x => x * 10])) (of (2) ([3]))
// => of(3)([4, 20, 30])

chain :: (a -> NonEmptyArray b) -> NonEmptyArray a -> NonEmptyArray b

Applies `f` to every element and concatenates the resulting `NonEmptyArrays`.
Because `f` produces a `NonEmptyArray` for each input and both the input and each result
are non-empty, the overall result is guaranteed non-empty. Use this when each element
expands into multiple elements — an operation sometimes called `concatMap` or `flatMap`.

chain (x => of (x) ([x * 2])) (of (1) ([2]))
// => of(1)([2, 2, 4])
chain (x => singleton (x + 10)) (of (1) ([2, 3]))
// => of(11)([12, 13])

reduce :: (b -> a -> b) -> b -> NonEmptyArray a -> b

Left fold over all elements with a curried binary function and an explicit initial value.
Converts the `NonEmptyArray` to a plain array and reduces it, starting from `init`.
Use `reduce1` instead when the fold combines elements of the same type and the first
element can serve as the natural seed value.

reduce (acc => x => acc + x) (0) (of (1) ([2, 3]))
// => 6
reduce (acc => x => [...acc, x * 2]) ([]) (of (1) ([2, 3]))
// => [2, 4, 6]

reduce1 :: (a -> a -> a) -> NonEmptyArray a -> a

Left fold using the `head` as the initial accumulator — no explicit seed needed.
This is the key advantage of `NonEmptyArray` over plain `Array`: `foldl1` (Haskell's name)
is unconditionally safe. Use it to reduce homogeneous non-empty arrays — summing numbers,
merging objects, or finding the maximum — without providing a dummy initial value.

reduce1 (acc => x => acc + x) (of (1) ([2, 3]))
// => 6
reduce1 (acc => x => Math.max (acc, x)) (of (3) ([1, 4, 1, 5]))
// => 5

filter :: (a -> Boolean) -> NonEmptyArray a -> Maybe (NonEmptyArray a)

Filters elements by a predicate, returning `Just(NonEmptyArray)` if any pass and `Nothing` if all fail.
The result is wrapped in `Maybe` because filtering can empty the array — which would violate the
`NonEmptyArray` invariant. Handle the `Nothing` case explicitly to avoid silently losing all elements.

filter (x => x > 1) (of (1) ([2, 3]))
// => just(of(2)([3]))
filter (x => x > 99) (of (1) ([2, 3]))
// => nothing()

reverse :: NonEmptyArray a -> NonEmptyArray a

Reverses the `NonEmptyArray`, placing the last element first and the first element last.
Reversing can never empty the array, so the result remains a `NonEmptyArray` — no `Maybe`
needed. Use this before `head` or `last` to access the "other end" without converting
to a plain array first.

reverse (of (1) ([2, 3]))
// => of(3)([2, 1])
reverse (singleton (5))
// => singleton(5)

append :: a -> NonEmptyArray a -> NonEmptyArray a

Appends a single element to the end of the `NonEmptyArray`.
Adding an element can never empty the array, so the result is still a `NonEmptyArray`.
This is more semantically clear than spreading into a new `of` call, and composes
naturally in a `reduce` pipeline that accumulates elements one at a time.

append (4) (of (1) ([2, 3]))
// => of(1)([2, 3, 4])
append ('end') (singleton ('start'))
// => of('start')(['end'])

prepend :: a -> NonEmptyArray a -> NonEmptyArray a

Prepends a single element to the front of the `NonEmptyArray`.
The prepended element becomes the new `head`, shifting all existing elements into the `tail`.
Like `append`, this preserves the `NonEmptyArray` invariant — adding an element can never
produce an empty array.

prepend (0) (of (1) ([2, 3]))
// => of(0)([1, 2, 3])
prepend ('start') (singleton ('end'))
// => of('start')(['end'])

sort :: (a -> a -> Ordering) -> NonEmptyArray a -> NonEmptyArray a

Stably sorts the `NonEmptyArray` using a curried `Ordering` comparator.
Sorting can never empty the array, so the result is still a `NonEmptyArray` — no wrapping
in `Maybe` is required. Elements that compare as `EQ` retain their original relative order,
making this safe to chain with `concatComparators` for multi-field sorts.

sort (compare) (of (3) ([1, 2]))
// => of(1)([2, 3])
sort (compare) (of ('banana') (['apple', 'cherry']))
// => of('apple')(['banana', 'cherry'])

traverse :: (b -> f b) -> (f (a->b) -> f a -> f b) -> ((a->b) -> f a -> f b) -> (a -> f b) -> NonEmptyArray a -> f (NonEmptyArray b)

Applicative traversal — maps `f` over every element collecting effects into the outer applicative.
Because the array is non-empty, at least one effect is always run and the result is always
non-empty inside the applicative. Accepts explicit `apOf`/`apAp`/`apMap` to remain
functor-agnostic and work with any applicative functor.

apOf :: b -> f b (pure / of)
apAp :: f (a -> b) -> f a -> f b (ap, curried)
apMap :: (a -> b) -> f a -> f b (map, curried)

const apOf  = Array.of
const apAp  = ff => fa => ff.flatMap (f => fa.map (f))
const apMap = f  => fa => fa.map (f)
traverse (apOf) (apAp) (apMap) (x => [x, -x]) (of (1) ([2]))
// => [of(1)([2]), of(1)([-2]), of(-1)([2]), of(-1)([-2])]

Number

Number predicates and operations.

isNum :: a -> Boolean

Returns `true` when the value is a `number` primitive that is not `NaN`. In
JavaScript, `typeof NaN === 'number'`, so this guard is essential before performing
arithmetic to avoid silent `NaN` propagation. Use it as a predicate in `filter` to
discard non-numeric values, or as a precondition check in numeric pipelines.

isNum (42)
// => true
isNum (NaN)
// => false
isNum ('42')
// => false

equals :: Number -> Number -> Boolean

Compares two numbers for structural equality using `Object.is` semantics: `NaN`
equals `NaN`, and `+0` equals `-0`. Unlike `===`, this makes equality reflexive —
every value equals itself. The curried form is useful as a predicate in
`Array.prototype.find` or for building equality-based lookup functions.

equals (NaN) (NaN)
// => true
equals (3) (3)
// => true
equals (1) (2)
// => false

lte :: Number -> Number -> Boolean

Returns `true` when `a ≤ b` under a total order where `NaN` is treated as less than
every other number. A total order is required by sorting and range-checking
algorithms that would break down if any pair of values were incomparable. The
curried form is the foundation from which `lt`, `gte`, and `gt` are all derived.

lte (1) (2)
// => true
lte (2) (2)
// => true
lte (3) (2)
// => false

lt :: Number -> Number -> Boolean

Returns `true` when `a` is strictly less than `b`. Being curried, `lt (5)` can be
partially applied to produce a reusable "is-less-than-5" predicate that composes
cleanly into `filter` or conditional branching without a wrapper lambda.

lt (1) (2)
// => true
lt (2) (2)
// => false

gte :: Number -> Number -> Boolean

Returns `true` when `a ≥ b`, derived from `lte` by flipping its arguments. The
curried form — `gte (0)` — produces a reusable "non-negative" predicate suitable
for `filter` calls over numeric arrays.

gte (2) (1)
// => true
gte (2) (2)
// => true
gte (1) (2)
// => false

gt :: Number -> Number -> Boolean

Returns `true` when `a` is strictly greater than `b`. The curried form `gt (0)` gives
a reusable positive-number predicate that can be passed directly to `filter` without
writing an arrow function.

gt (2) (1)
// => true
gt (2) (2)
// => false

min :: Number -> Number -> Number

Returns the smaller of two numbers using the same total order as `lte`. Use the
curried form in `reduce` to fold an array down to its minimum value without
importing a separate utility.

min (1) (2)
// => 1
min (5) (3)
// => 3

max :: Number -> Number -> Number

Returns the larger of two numbers using the same total order as `lte`. Use the
curried form in `reduce` to fold an array down to its maximum value without
importing a separate utility.

max (1) (2)
// => 2
max (5) (3)
// => 5

clamp :: Number -> Number -> Number -> Number

Constrains a number to the closed interval `[lo, hi]`, returning `lo` if the value
is below the range and `hi` if it is above. This is a common need when normalising
user input or keeping values within API or hardware limits. The three-argument
curried form allows the bounds to be fixed once and reused across many values.

clamp (0) (10) (15)
// => 10
clamp (0) (10) (-3)
// => 0
clamp (0) (10) (5)
// => 5

negate :: Number -> Number

Returns the arithmetic negation of a number. It is the functional equivalent of the
unary minus operator, making it composable in `map` or `pipe` chains where operators
are not first-class values.

negate (3)
// => -3
negate (-7)
// => 7

add :: Number -> Number -> Number

Returns the sum of two numbers. Its curried form — `add (n)` — produces a reusable
incrementer that can be mapped over arrays without writing an arrow function. This is
especially useful in `pipe` or `map` calls where you want to shift all values by a
constant offset.

add (1) (2)
// => 3
[1, 2, 3].map (add (10))
// => [11, 12, 13]

sub :: Number -> Number -> Number

Returns `x - y`, where `y` (the subtrahend) is provided first. This argument order
means `sub (1)` produces a "decrement by 1" function, matching the FP convention of
fixing the "what to subtract" operand and varying the minuend. For example,
`map (sub (1))` decrements every element of an array.

sub (1) (3)
// => 2
sub (10) (100)
// => 90

mult :: Number -> Number -> Number

Returns the product of two numbers. Partially applying it — `mult (2)` — gives a
reusable doubling function that composes directly into `map` or `pipe` chains without
boilerplate lambdas, making scaling transformations concise and readable.

mult (2) (3)
// => 6
[1, 2, 3].map (mult (3))
// => [3, 6, 9]

div :: Number -> Number -> Number

Returns `x / y`, where `y` (the divisor) is provided first. The argument order means
`div (100)` produces a "divide by 100" function, ready to be mapped over raw values
to produce percentages or normalised numbers. Beware that dividing by zero yields
`Infinity` rather than throwing.

div (2) (10)
// => 5
div (100) (50)
// => 0.5

pow :: Number -> Number -> Number

Returns `base ** exp`, where the exponent is provided first. Partially applying it —
`pow (2)` — gives a squaring function that can be mapped over any numeric array.
This argument order mirrors how mathematical notation reads: "raise to the power of 2."

pow (2) (3)
// => 9
pow (3) (2)
// => 8

abs :: Number -> Number

Returns the absolute (non-negative) magnitude of a number, stripping its sign. It
wraps `Math.abs` as a single-argument function, making it composable in `map` or
`pipe` chains wherever a point-free transformer is needed.

abs (-3)
// => 3
abs (3)
// => 3

rem :: Number -> Number -> Number

Returns the remainder of `x / n` (i.e. `x % n`), with the sign following the
dividend — JavaScript's `%` semantics, not floored modulo. Use the curried form
`rem (n)` as a pipeline-ready function to test divisibility or rotate through a
fixed-size cycle. Note that the result can be negative when `x` is negative.

rem (3) (10)
// => 1
rem (3) (-10)
// => -1

round :: Number -> Integer

Rounds a number to the nearest integer, with ties rounding up (`0.5` → `1`). It
wraps `Math.round` as a single-argument function so it can be composed in `map` or
`pipe` chains without wrapping it in a lambda.

round (3.5)
// => 4
round (3.4)
// => 3

floor :: Number -> Integer

Returns the largest integer less than or equal to `n` (rounds toward negative
infinity). Wraps `Math.floor` as a composable function; use it in pipelines to
truncate decimal values safely. Note that for negative numbers this rounds away
from zero (e.g. `-3.1` → `-4`).

floor (3.9)
// => 3
floor (-3.1)
// => -4

ceil :: Number -> Integer

Returns the smallest integer greater than or equal to `n` (rounds toward positive
infinity). Wraps `Math.ceil` as a composable function; use it when you need
conservative over-estimates, such as calculating page counts from item counts.

ceil (3.1)
// => 4
ceil (-3.9)
// => -3

sum :: Array Number -> Number

Sums all numbers in an array, returning `0` for an empty array (the identity for
addition). This is equivalent to a left fold with `(+)` and is useful for
aggregating the results of a `map` or `filter` step at the end of a pipeline.

sum ([1, 2, 3])
// => 6
sum ([])
// => 0

product :: Array Number -> Number

Multiplies all numbers in an array together, returning `1` for an empty array (the
identity for multiplication). Use it to compute factorials, probabilities, or any
multiplicative aggregate in a single step at the end of a pipeline.

product ([2, 3, 4])
// => 24
product ([])
// => 1

even :: Integer -> Boolean

Returns `true` when an integer is evenly divisible by 2. The unary form makes it
directly usable as a predicate in `filter (even)` or `find (even)` without wrapping
it in an arrow function.

even (4)
// => true
even (3)
// => false

odd :: Integer -> Boolean

Returns `true` when an integer is not evenly divisible by 2. Like `even`, it is
predicate-ready and can be passed directly to `filter` or `find` without a wrapper.

odd (3)
// => true
odd (4)
// => false

parseFloat_ :: String -> Maybe Number

Parses a string as a floating-point number using a strict grammar, returning
`Just(n)` on success and `Nothing` on any invalid input. Unlike the native
`parseFloat`, it rejects strings with leading junk (e.g. `'12abc'`) and empty
strings, making it safe for pipelines that process untrusted string data.

parseFloat_ ('3.14')
// => just(3.14)
parseFloat_ ('-2.5e3')
// => just(-2500)
parseFloat_ ('abc')
// => nothing()

parseFloat_E :: String -> Either String Number

Like `parseFloat_`, but returns `Right(n)` on success and `Left(errorMessage)` on
failure, providing a self-describing error suitable for logging or display. Prefer
this variant in contexts where you need to report *why* parsing failed rather than
simply discarding the value with a `Nothing`.

parseFloat_E ('3.14')
// => right(3.14)
parseFloat_E ('abc')
// => left('not a valid float: "abc"')

parseInt_ :: Integer -> String -> Maybe Integer

Parses a string as an integer in the given radix (2–36), returning `Just(n)` on
valid input or `Nothing` otherwise. Unlike the built-in `parseInt`, it rejects
strings containing characters invalid for the chosen base and rejects radices outside
2–36, preventing silent partial parses.

parseInt_ (16) ('ff')
// => just(255)
parseInt_ (2) ('1010')
// => just(10)
parseInt_ (10) ('abc')
// => nothing()

parseInt_E :: Integer -> String -> Either String Integer

Like `parseInt_`, but returns `Right(n)` on success and `Left(errorMessage)` on
failure, giving a descriptive error when the input or radix is invalid. Use this
variant when you need to surface parsing errors to the caller rather than silently
discarding them with a `Nothing`.

parseInt_E (16) ('ff')
// => right(255)
parseInt_E (10) ('abc')
// => left('not a valid base-10 integer: "abc"')
parseInt_E (99) ('1')
// => left('invalid radix: 99 (must be 2–36)')

Ordering

The Ordering type (LT | EQ | GT).

Ordering is the canonical result type of a comparison. It forms a Monoid
where the empty element is EQ and concat keeps the first non-EQ value —
which makes it trivial to chain multi-field comparators.

Ordering = LT | EQ | GT

lt :: Ordering

The "less-than" Ordering — a constant value, not a function.
Returned by `compare(a)(b)` when `a < b`, and used directly when constructing sort keys
or as input to `concat` for multi-field comparison chains.

lt
// => { tag: 'LT' }

eq :: Ordering

The "equal" Ordering — a constant value, not a function.
This is also the identity element (empty) of the Ordering `Monoid`: `concat(eq)(x) === x`
for any `x`. Use `eq` directly when you want to express "no preference" in a sort key,
or as the starting value when folding a list of comparators.

eq
// => { tag: 'EQ' }

gt :: Ordering

The "greater-than" Ordering — a constant value, not a function.
Returned by `compare(a)(b)` when `a > b`. Combine with `invert` to turn a `gt` into `lt`
and thereby reverse the sort direction without rewriting the entire comparator.

gt
// => { tag: 'GT' }

isOrdering :: a -> Boolean

Returns `true` when the value is one of the three `Ordering` constants: `lt`, `eq`, or `gt`.
Use as a runtime guard when accepting comparison results from external sources or when
asserting that a custom comparator function actually returns a valid `Ordering`.

isOrdering (lt)
// => true
isOrdering (eq)
// => true
isOrdering (0)
// => false

isLT :: Ordering -> Boolean

Returns `true` when the Ordering is LT.
Use as a guard when you need to branch specifically on the "less than" case, for example
to apply a discount only when the input is below a threshold.

isLT (lt)
// => true
isLT (eq)
// => false

isEQ :: Ordering -> Boolean

Returns `true` when the Ordering is EQ.
Use as a guard to detect a tie before falling through to a secondary comparator,
or to confirm that two values compare as identical under a given sort key.

isEQ (eq)
// => true
isEQ (lt)
// => false

isGT :: Ordering -> Boolean

Returns `true` when the Ordering is GT.
Use as a guard to detect the "greater than" case — for example to reject values that
exceed a maximum, or to apply a surcharge when demand outpaces a threshold.

isGT (gt)
// => true
isGT (eq)
// => false

equals :: Ordering -> Ordering -> Boolean

Returns `true` when both `Ordering` values share the same constructor tag.
Since there are only three distinct `Ordering` values, this is effectively reference equality.
Use it when you need to compare two comparison results — for example, asserting that a sort
key produces the expected outcome in a test.

equals (lt) (lt)
// => true
equals (eq) (eq)
// => true
equals (lt) (gt)
// => false

concat :: Ordering -> Ordering -> Ordering

The `Monoid` concatenation for `Ordering` — returns the first non-`EQ` value, or `EQ` if both are equal.
This encodes lexicographic priority: the first comparison that "breaks the tie" wins, and `EQ`
means "defer to the next comparator". Chain multiple `concat` calls — or use `concatComparators`
— to sort by a primary key, then a secondary key, then a tertiary key, and so on.

concat (lt) (gt)
// => lt
concat (eq) (gt)
// => gt
concat (eq) (eq)
// => eq

empty :: Ordering

The identity element of the `Ordering` `Monoid` — equal to `eq`.
`concat(empty)(x)` is always `x`, making `empty` the correct starting value when
folding a list of `Ordering` values, such as when reducing a sequence of sort keys
down to a single comparison result.

empty
// => { tag: 'EQ' }

ordering :: a -> a -> a -> Ordering -> a

Case analysis on an `Ordering` — returns one of three values based on the constructor.
The arguments are plain values, not functions, because `Ordering` carries no payload.
Use this as a clean alternative to `if (isLT(o)) ... else if (isGT(o)) ...` chains —
for example when mapping an `Ordering` to a CSS class, a sign character, or a label.

ordering ('ascending') ('equal') ('descending') (lt)
// => 'ascending'
ordering ('ascending') ('equal') ('descending') (eq)
// => 'equal'
ordering ('ascending') ('equal') ('descending') (gt)
// => 'descending'

toNumber :: Ordering -> -1 | 0 | 1

Converts an `Ordering` to the conventional JS comparator integer: `-1`, `0`, or `1`.
Use this when bridging to APIs that expect a native comparator number — for example,
passing an `Ordering`-based comparator to `Array.prototype.sort` via `toComparator`,
or serialising a comparison result as a numeric score.

toNumber (lt)
// => -1
toNumber (eq)
// => 0
toNumber (gt)
// => 1

fromNumber :: Number -> Ordering

Converts a native JS comparator integer (negative / zero / positive) to an `Ordering`.
Use this to wrap the raw numeric output of `String.prototype.localeCompare`, `Date`
subtraction, or any other signed-number comparison, bringing it into the `Ordering` world
where it can be composed with `concat`, `invert`, and `concatComparators`.

fromNumber (-3)
// => lt
fromNumber (0)
// => eq
fromNumber (7)
// => gt

compare :: a -> a -> Ordering

Compares two values using JavaScript's built-in `<` and `>` operators, returning an `Ordering`.
This is the primary building block for sort keys — pass it directly to `sortWith`, or combine
with `comparing` to project a field before comparing. Works correctly for numbers, strings,
and any other type that supports native JS ordering.

compare (1) (2)
// => lt
compare ('banana') ('apple')
// => gt
compare (5) (5)
// => eq

comparing :: Ord b => (a -> b) -> a -> a -> Ordering

Builds an `Ordering` comparator by projecting both values through `f` before comparing.
This is the primary tool for sorting objects by a field — equivalent to Haskell's `comparing`.
Combine multiple `comparing` calls with `concatComparators` for multi-field sorts, and wrap
the result with `invert` to sort descending.

comparing (x => x.age) ({ age: 25 }) ({ age: 30 })
// => lt
comparing (x => x.name) ({ name: 'alice' }) ({ name: 'alice' })
// => eq
comparing (x => x.score) ({ score: 99 }) ({ score: 80 })
// => gt

invert :: Ordering -> Ordering

Reverses an `Ordering` — swaps `LT` with `GT` and keeps `EQ` unchanged.
The primary way to flip a comparator to sort descending: wrap any `comparing` call with
`a => b => invert (comparator (a) (b))` or use it after `concatComparators` to reverse
the combined ordering without rebuilding the comparator.

invert (lt)
// => gt
invert (gt)
// => lt
invert (eq)
// => eq

toComparator :: (a -> a -> Ordering) -> (a, a) -> Number

Converts a curried `Ordering` comparator to a native JS `(a, b) => number` comparator,
suitable for passing directly to `Array.prototype.sort`. This bridges the functional
`Ordering` world to the imperative sorting API, letting you build composable comparators
with `comparing` and `concatComparators` and then discharge them in a single `.sort()` call.

[3, 1, 2].sort (toComparator (compare))
// => [1, 2, 3]
[{ n: 3 }, { n: 1 }].sort (toComparator (comparing (x => x.n)))
// => [{ n: 1 }, { n: 3 }]

sortWith :: (a -> a -> Ordering) -> Array a -> Array a

Performs a stable sort of an array using a curried `Ordering` comparator.
Unlike the native `Array.prototype.sort`, this is non-mutating and guarantees stability
by using the original indices as a tiebreaker. Use it instead of `arr.sort(toComparator(cmp))`
to keep the original order of equal elements intact.

sortWith (compare) ([3, 1, 2])
// => [1, 2, 3]
sortWith (comparing (x => x.name)) ([{ name: 'charlie' }, { name: 'alice' }, { name: 'bob' }])
// => [{ name: 'alice' }, { name: 'bob' }, { name: 'charlie' }]

concatComparators :: (a -> a -> Ordering) -> (a -> a -> Ordering) -> a -> a -> Ordering

Combines two `Ordering` comparators — the second is consulted only when the first returns `EQ`.
This is `concat` lifted to comparator functions and is the idiomatic tool for multi-field
sorting: sort by the primary key first, break ties with the secondary key, and chain further
calls for tertiary keys. Pass the result directly to `sortWith` or `toComparator`.

const byName = comparing (x => x.name)
const byAge  = comparing (x => x.age)
sortWith (concatComparators (byName) (byAge)) ([{ name: 'alice', age: 30 }, { name: 'alice', age: 25 }, { name: 'bob', age: 20 }])
// => [{ name: 'alice', age: 25 }, { name: 'alice', age: 30 }, { name: 'bob', age: 20 }]

Pair

Ordered pair (2-tuple) utilities.
A pair is represented as a 2-element array [a, b].

pair :: a -> b -> [a, b]

Constructs an ordered 2-tuple — a `[a, b]` array with labelled slot semantics.
Pairs are the simplest algebraic product type: they hold exactly two values of potentially
different types. Use `pair` rather than bare array literals to signal intent and to work
with the rest of the pair combinators (`fst`, `snd`, `bimap`, `swap`, etc.).

pair ('alice') (30)
// => ['alice', 30]
pair ('GET') ('/api/users')
// => ['GET', '/api/users']

of :: a -> [a, a]

Duplicates a single value into both slots of a pair — `of(a)` produces `[a, a]`.
Also known as `dup` in some FP libraries. Useful as the starting point for a `bimap`
pipeline where you want to derive two different projections from the same input value.

of (5)
// => [5, 5]
of ('hello')
// => ['hello', 'hello']

fst :: [a, b] -> a

Extracts the first element of a pair — the `a` in `[a, b]`.
The symmetric counterpart to `snd`. Useful in pipelines that `map` over arrays of pairs,
for example extracting all keys from a list of key-value pairs.

fst (['alice', 30])
// => 'alice'
fst ([['GET', '/users'], 200])
// => ['GET', '/users']

snd :: [a, b] -> b

Extracts the second element of a pair — the `b` in `[a, b]`.
The symmetric counterpart to `fst`. Use it to project the "value" side in a collection
of key-value pairs, or to recover the mapped element after a `traverse`.

snd (['alice', 30])
// => 30
snd (['GET', '/api/users'])
// => '/api/users'

fold :: (a -> b -> c) -> [a, b] -> c

The canonical eliminator for a pair — applies a curried binary function to both elements.
Equivalent to Haskell's `uncurry`: it converts a curried `a -> b -> c` into a function that
accepts `[a, b]`. Use it to collapse a pair into a single value without destructuring manually,
or to bridge between curried pair combinators and a function that expects two separate arguments.

fold (a => b => a + b) ([3, 4])
// => 7
fold (key => val => `${key}=${val}`) (['color', 'red'])
// => 'color=red'

map :: (b -> d) -> [a, b] -> [a, d]

Maps `f` over the second element, leaving the first unchanged — the standard `Functor` instance.
The first element acts as a fixed "context" (like the key in a key-value pair) while the second
is the "value" being transformed. This is consistent with the `Functor` instance for pairs in
Haskell and lets you pipeline transformations on the value slot without touching the context.

map (x => x * 2) (['score', 21])
// => ['score', 42]
map (s => s.toUpperCase()) (['key', 'hello'])
// => ['key', 'HELLO']

mapFst :: (a -> c) -> [a, b] -> [c, b]

Maps `f` over the first element, leaving the second unchanged. Use this when the
"context" or key slot needs to be transformed — for example, normalising string keys
to lowercase or converting an ID to a different format — without touching the associated value.

mapFst (s => s.toLowerCase()) (['USER', 42])
// => ['user', 42]
mapFst (n => n * 10) ([3, 'hello'])
// => [30, 'hello']

mapSnd :: (b -> d) -> [a, b] -> [a, d]

Maps `f` over the second element, leaving the first unchanged.
An explicit alias for `map` — prefer `mapSnd` over `map` when the intent is specifically
to transform the second slot, making the direction of the transformation immediately
clear at the call site.

mapSnd (n => n + 1) (['count', 9])
// => ['count', 10]
mapSnd (s => s.trim()) (['name', '  alice  '])
// => ['name', 'alice']

bimap :: (a -> c) -> (b -> d) -> [a, b] -> [c, d]

Applies `f` to both elements independently, transforming both slots in one pass.
This is the `Bifunctor` interface for pairs. Use it instead of chaining `mapFst` and
`mapSnd` when you need to remap both elements simultaneously — for example, normalising
a key-value pair or converting both members to a common representation.

bimap (s => s.toLowerCase()) (n => n * 2) (['SCORE', 21])
// => ['score', 42]
bimap (Number) (Boolean) (['42', 1])
// => [42, true]

ap :: [a -> b, a] -> b

Applies the function in the first slot to the value in the second — `[f, x]` becomes `f(x)`.
The pair acts as a self-contained argument bundle, pairing a computation with its input.
This is useful when you have accumulated both the function and its argument over separate
pipeline stages and need to discharge the application in a single step.

ap ([x => x * 2, 21])
// => 42
ap ([s => s.trim(), '  hello  '])
// => 'hello'

swap :: [a, b] -> [b, a]

Swaps the two elements, producing `[b, a]` from `[a, b]`. Useful when an API or
algorithm returns pairs in the opposite order from what a downstream combinator expects —
for example switching between `(key, value)` and `(value, key)` conventions, or reversing
the slots before applying `map` or `fold`.

swap (['alice', 30])
// => [30, 'alice']
swap (['key', 'value'])
// => ['value', 'key']

traverse :: (b -> f b) -> ((a -> b) -> f a -> f b) -> (a -> f b) -> [a, c] -> f [b, c]

Traverses the first element through an applicative functor, keeping the second as a fixed
context in the result. This is the `Traversable` instance for the first slot of a pair,
letting you sequence effects over the first element while the second rides along unchanged.
Accepts explicit `apOf`/`apMap` to remain functor-agnostic and work with any applicative.

apOf :: b -> f b (pure / of)
apMap :: (a -> b) -> f a -> f b (map, curried)

const apOf  = Array.of
const apMap = f => xs => xs.map (f)
traverse (apOf) (apMap) (x => [x, -x]) ([1, 'ctx'])
// => [[1, 'ctx'], [-1, 'ctx']]

Regexp

RegExp constructors, comparison, and matching utilities.
Capture groups are returned as plain strings; unmatched optional groups → ''.

equals :: RegExp -> RegExp -> Boolean

Returns `true` when both regexes have identical source and all flags match (global,
ignoreCase, multiline, dotAll, sticky, unicode). This structural comparison is
needed because JavaScript's `===` always returns `false` for two distinct regex
objects, even if they are functionally identical.

equals (/a/g) (/a/g)
// => true
equals (/a/g) (/a/i)
// => false

regex :: String -> String -> RegExp

Constructs a `RegExp` from a flags string and a source pattern string. The curried
form lets you fix the flags once and derive multiple regexes from different sources —
for example, `regex ('gi')` applied to several patterns for case-insensitive global
matching.

regex ('g') ('[0-9]+')
// => /[0-9]+/g
regex ('i') ('hello')
// => /hello/i

regexEscape :: String -> String

Escapes all regex metacharacters in a string so it can be safely embedded in a
`RegExp` pattern as a literal. Without escaping, characters like `.`, `*`, or `(`
would be interpreted as regex syntax, leading to incorrect or unsafe patterns.

regexEscape ('a.b')
// => 'a\\.b'
regexEscape ('(1+1)')
// => '\\(1\\+1\\)'

test :: RegExp -> String -> Boolean

Returns `true` when the pattern matches anywhere in `s`, safely resetting
`lastIndex` before and after the call. This makes it safe to use with stateful
sticky or global regexes, and the curried form — `test (/pattern/)` — is a
ready-made predicate for `filter`.

test (/^a/) ('abacus')
// => true
test (/^a/) ('banana')
// => false

match :: RegExp -> String -> Maybe (Array String)

Returns `Just` an array of capture groups if the pattern matches `s`, or `Nothing`
if there is no match. Unmatched optional groups are normalised to `''` instead of
`undefined`, making downstream processing simpler. The `Maybe` return type forces
callers to handle the no-match case explicitly.

match (/(\w+)\s(\w+)/) ('hello world')
// => just(['hello', 'world'])
match (/(\d+)/) ('abc')
// => nothing()

matchAll :: RegExp -> String -> Array (Array String)

Returns an array of capture-group arrays for every match of a global (`g`) pattern
against `s`. Unmatched optional groups are normalised to `''`, and `lastIndex` is
restored after the call. Use this to extract all occurrences of a structured pattern
from a string in one step.

matchAll (/(\w+)/g) ('hi there')
// => [['hi'], ['there']]
matchAll (/(\d+)/g) ('a1 b2 c3')
// => [['1'], ['2'], ['3']]

replace :: (Array String -> String) -> RegExp -> String -> String

Replaces pattern matches using a substitution function that receives the array of
capture groups for each match. Unlike the native `String.prototype.replace`, the
function receives only capture groups (not the full match or offsets), making it
easier to write focused, declarative transformations.

replace (([w]) => w.toUpperCase ()) (/(\w+)/) ('hello world')
// => 'HELLO world'
replace (() => 'X') (/a/) ('cat')
// => 'cXt'

State

The State monad.
State s a = s -> [a, s]

A State action is a plain function that takes a state and returns a pair
[value, newState]. There is no wrapper object — the type IS the function.
This keeps the implementation minimal and the interop with plain JS trivial.

All combinators are curried and follow the same style as the rest of sail.

state :: (s -> [a, s]) -> State s a

Wraps a state-transition function into the State monad.
The function must have type s -> [a, s].

state (s => [s + 1, s + 1]) // a State action that increments and returns

get :: State s s

Retrieves the current state as the result value without modifying it. This
makes the state observable inside a `chain` pipeline — once you have the
state as a value you can branch on it, compute from it, or pass it to the
next step. It is the fundamental read operation of the State monad.

run (get) (42)
// => [42, 42]
run (chain (s => of (s * s)) (get)) (7)
// => [49, 7]

put :: s -> State s null

Replaces the entire state with a given value, discarding the current state
and returning `null` as the result. Use it inside a `chain` pipeline when
you need to reset or completely overwrite the state — for example, to store a
value computed from the old state back after reading it with `get`.

run (put (99)) (0)
// => [null, 99]
run (chain (s => put (s * 2)) (get)) (7)
// => [null, 14]

modify :: (s -> s) -> State s null

Applies a transformation function to the current state and stores the result
as the new state, returning `null` as the result value. It is the most common
state operation in practice — a convenient shorthand for reading, transforming,
and writing back in one step. Ideal for incrementing counters, pushing to
stacks, or updating record fields inside a stateful pipeline.

run (modify (s => s + 1)) (0)
// => [null, 1]
run (modify (stack => ['item', ...stack])) ([])
// => [null, ['item']]
run (modify (s => ({ ...s, visited: s.visited + 1 }))) ({ visited: 4 })
// => [null, { visited: 5 }]

gets :: (s -> a) -> State s a

Projects a derived value from the current state without modifying it. It is
a convenient shorthand for `chain (s => of (f (s))) (get)` and is useful
whenever you need a computed slice of the state — such as the length of a
list or a field of a record — as the result of a pipeline step.

run (gets (s => s * 2)) (21)
// => [42, 21]
run (gets (s => s.length)) ([1, 2, 3])
// => [3, [1, 2, 3]]
run (gets (s => s.name.toUpperCase ())) ({ name: 'alice', role: 'admin' })
// => ['ALICE', { name: 'alice', role: 'admin' }]

run :: State s a -> s -> [a, s]

Executes a State computation starting from an initial state, returning a
pair `[result, finalState]`. This is the primary runner — the point at which
a pure description of stateful computation is interpreted into an actual
value and updated state. Call it at the edge of your program after building
a pipeline with `chain`, `modify`, and friends.

run (of (42)) (0)
// => [42, 0]
run (chain (n => of (n + 1)) (get)) (10)
// => [11, 10]
const counter = chain (() => get) (modify (n => n + 1))
run (counter) (0)
// => [1, 1]

eval_ :: State s a -> s -> a

Runs a State computation and returns only the result value, discarding the
final state. Use it when you care about the computed answer but have no
further need for the state — for example, evaluating an expression inside an
environment without needing the environment back.

eval_ (of (42)) (0)
// => 42
eval_ (gets (s => s * 2)) (21)
// => 42

exec :: State s a -> s -> s

Runs a State computation and returns only the final state, discarding the
result value. Use it when you care about the side-effect on the state rather
than the computed value — for example, after running a series of `modify`
steps to build up a data structure.

exec (put (99)) (0)
// => 99
exec (modify (s => s + 1)) (5)
// => 6
const push = val => modify (stack => [val, ...stack])
exec (chain (() => push (2)) (push (1))) ([])
// => [2, 1]

map :: (a -> b) -> State s a -> State s b

Transforms the result value produced by a State action while leaving the
state transition completely unchanged. It is the Functor instance for State,
letting you post-process a computation's output — format a number, extract a
field — without restructuring the stateful pipeline around it.

run (map (x => x * 2) (of (21))) (0)
// => [42, 0]
run (map (x => x.toUpperCase ()) (gets (s => s.name))) ({ name: 'alice' })
// => ['ALICE', { name: 'alice' }]

of :: a -> State s a

Lifts a plain value into State without inspecting or modifying the state in
any way. It is the `pure`/`return` of the State monad — the identity action
that simply injects a known result into a stateful pipeline. Use it to
convert ordinary values into State actions so they can participate in `chain`
or `ap` compositions.

run (of (42)) (99)
// => [42, 99]
run (of ('hello')) ({ count: 0 })
// => ['hello', { count: 0 }]

ap :: State s (a -> b) -> State s a -> State s b

Applies a State action that carries a function to a State action that carries
a value, threading state through both in sequence. It is the Applicative
instance for State, enabling you to combine independent stateful computations
without a full `chain` when the second action does not depend on the first's
result. Prefer `chain` when there is a data dependency between steps.

run (ap (of (x => x + 1)) (of (41))) (0)
// => [42, 0]
run (ap (gets (s => x => x + s)) (of (10))) (5)
// => [15, 5]

chain :: (a -> State s b) -> State s a -> State s b

Sequences two State actions so the second can depend on the value produced
by the first, with state flowing through both steps in order. It is the
monadic bind (`>>=`) for State and the primary building block for stateful
pipelines: each step may read and modify the current state, and the updated
state is automatically forwarded to every subsequent step without any shared
mutable variable.

const action = chain (n => put (n * 2)) (get)
run (action) (5)
// => [null, 10]
const push = val => modify (stack => [val, ...stack])
const action2 = chain (() => gets (s => s.length)) (push ('a'))
run (action2) ([])
// => [1, ['a']]

andThen :: State s b -> State s a -> State s b

Sequences two State actions, discarding the value of the first.
Haskell: (>>)

run (andThen (of (42)) (put (99))) (0)
// => [42, 99]

chainRec :: ((a -> Step, b -> Step, a) -> State s Step) -> a -> State s b

Stack-safe tail-recursive State bind (Fantasy Land ChainRec).
f receives (next, done, a) and must return a State action that yields a Step.

// Count down from n, accumulating increments in state:
const action = chainRec ((next, done, n) =>
  n <= 0 ? of (done (0)) : chain ((_) => of (next (n - 1))) (modify (s => s + 1))
) (5)
run (action) (0)
// => [0, 5]

sequence :: Array (State s a) -> State s (Array a)

Runs a list of State actions in sequence, collecting all values.
The state threads through each action left to right.

run (sequence ([of (1), of (2), of (3)])) (0)
// => [[1, 2, 3], 0]

traverse :: (a -> State s b) -> Array a -> State s (Array b)

Maps f over an array, running each resulting State action in sequence and
collecting the values. Equivalent to sequence (xs.map (f)).

run (traverse (x => of (x * 2)) ([1, 2, 3])) (0)
// => [[2, 4, 6], 0]

lift2 :: (a -> b -> c) -> State s a -> State s b -> State s c

Runs two State actions and combines their values with a curried binary
function. Useful for point-free style when you don't need intermediate
binds.

run (lift2 (a => b => a + b) (of (1)) (of (2))) (0)
// => [3, 0]

StateEither

StateT s (Either e) a
StateT s e a = s -> Either e [a, s]

A StateT action is a plain function s -> Either e [a, s].
Right([value, newState]) — success with a new state.
Left(error) — failure; the state is discarded.

This is the standard State monad with an Either error layer added.
Use it when individual steps can fail AND you need to thread state
through those steps — binary parsers, stateful validators, interpreters.

Relationship to State:
State s a = s -> [a, s]
StateT s e a = s -> Either e [a, s]

All combinators are curried and follow the same style as state.js.

state :: (s -> Either e [a, s]) -> StateT s e a

The low-level constructor for `StateT` — wraps a raw function
`s -> Either e [a, s]` directly. Most of the time you will use higher-level
helpers like `of`, `get`, or `lift`, but `state` is available when you need
to write a custom transition that cannot be expressed with those combinators.

const increment = state (n => E.right ([n, n + 1]))
run (increment) (0)
// => right([0, 1])

get :: StateT s e s

Reads the current state and exposes it as the action's result value without
modifying it. This is the fundamental "read" primitive for `StateT` —
combine it with `chain` to inspect the state mid-computation and branch on
its value or extract a field for further processing.

run (get) (42)
// => right([42, 42])
run (chain (n => of (n > 0 ? 'positive' : 'non-positive')) (get)) (-1)
// => right(['non-positive', -1])

put :: s -> StateT s e null

Replaces the current state with the given value and returns `null` as the
result. Use `put` to unconditionally overwrite the state — for example,
resetting a counter to zero, storing a newly fetched record, or advancing
a cursor to a new position in a stateful computation.

run (put ({ count: 0, step: 1 })) ({ count: 5, step: 1 })
// => right([null, { count: 0, step: 1 }])
run (put (99)) (0)
// => right([null, 99])

modify :: (s -> s) -> StateT s e null

Updates the current state by applying `f` to it and replacing it with the
result, returning `null` as the action's value. More expressive than `put`
when the new state depends on the old one — for example, incrementing a
counter, appending to a list, or toggling a boolean field.

run (modify (s => s + 1)) (41)
// => right([null, 42])
run (modify (s => ({ ...s, count: s.count + 1 }))) ({ count: 4, errors: 0 })
// => right([null, { count: 5, errors: 0 }])

gets :: (s -> a) -> StateT s e a

Like `get`, but applies a projection function to the state before returning
it as the result value, leaving the state unchanged. Use `gets` to read a
specific field from a complex state object without first fetching the whole
state and manually destructuring it.

run (gets (s => s.count)) ({ count: 7, errors: 0 })
// => right([7, { count: 7, errors: 0 }])
run (gets (s => s.length)) ([1, 2, 3])
// => right([3, [1, 2, 3]])

lift :: Either e a -> StateT s e a

Promotes a standalone `Either e a` value into the `StateT` context,
threading the current state through unchanged. This is the canonical way to
incorporate a failing operation that has no state dependency — such as input
validation or a parse result — into a stateful pipeline.

run (lift (E.right (42))) (0)
// => right([42, 0])
run (lift (E.left ('invalid input'))) ({ step: 3 })
// => left('invalid input')

run :: StateT s e a -> s -> Either e [a, s]

Executes a `StateT` action from an initial state and returns the full result
as `Either e [value, finalState]`. This is the primary way to "escape" the
`StateT` abstraction at the edge of your program — after this call you have
a plain `Either` you can pattern-match or pass to further error-handling.

run (of (42)) (0)
// => right([42, 0])
run (chain (n => put (n * 2)) (get)) (5)
// => right([null, 10])

eval_ :: StateT s e a -> s -> Either e a

Like `run`, but discards the final state and returns only the computed value
inside `Either`. Use this when the state is an implementation detail and the
caller only cares about the result — for example, evaluating a stateful
parser and returning just the parsed value.

eval_ (of (42)) (0)
// => right(42)
eval_ (chain (n => of (n + 1)) (get)) (10)
// => right(11)

exec :: StateT s e a -> s -> Either e s

Like `run`, but discards the result value and returns only the final state
inside `Either`. Use this when you care about the side-effected state rather
than the produced value — for example, running a batch of `put`/`modify`
operations and then inspecting the resulting state.

exec (put (99)) (0)
// => right(99)
exec (modify (s => s + 1)) (41)
// => right(42)

map :: (a -> b) -> StateT s e a -> StateT s e b

Transforms the result value of a `StateT` action with `f`, leaving the state
thread untouched. If the action produced `Left`, the error propagates and `f`
is never called. Use `map` to post-process a result without re-entering the
monadic context.

run (map (n => n * 2) (of (21))) (0)
// => right([42, 0])
run (map (n => n + 1) (lift (E.left ('err')))) (0)
// => left('err')

of :: a -> StateT s e a

Injects a pure value into the `StateT` context without performing any state
transition or producing an error. This is the Applicative `pure` / Monad
`return` for `StateT` and serves as the terminal step in any `chain` pipeline
that succeeds with a known value.

run (of ('ok')) (42)
// => right(['ok', 42])
run (of (null)) ({ count: 0 })
// => right([null, { count: 0 }])

ap :: StateT s e (a -> b) -> StateT s e a -> StateT s e b

Applies a `StateT` action containing a function to a `StateT` action
containing a value, threading state through both in sequence. The state is
first modified by the function action, then by the value action. Short-
circuits at the first `Left`, so subsequent steps are not run.

run (ap (of (x => x + 1)) (of (41))) (0)
// => right([42, 0])
run (ap (lift (E.left ('err'))) (of (1))) (0)
// => left('err')

chain :: (a -> StateT s e b) -> StateT s e a -> StateT s e b

Sequences two `StateT` actions where the second action is produced by
applying `f` to the result of the first. This is the core monadic bind,
allowing each step to inspect the previous result and decide what to do
next — including failing with `lift(E.left(...))`. Short-circuits immediately
if any step produces `Left`.

run (chain (n => of (n * 2)) (of (21))) (0)
// => right([42, 0])
run (chain (n => n > 0 ? put (n) : lift (E.left ('non-positive'))) (get)) (-1)
// => left('non-positive')

andThen :: StateT s e b -> StateT s e a -> StateT s e b

Sequences two `StateT` actions, discarding the result of the first and
returning the result of the second. Use this when you only care about the
side-effect of the first action (e.g. a `modify` or `put`) and want to chain
a subsequent computation cleanly without an ignored bind variable.

run (andThen (of (42)) (put (99))) (0)
// => right([42, 99])
run (andThen (gets (s => s + 1)) (modify (s => s * 2))) (3)
// => right([7, 6])

chainFirst :: (a -> StateT s e b) -> StateT s e a -> StateT s e a

Runs a side-effecting `StateT` action `f` on the current result value but
keeps the original value (not the result of `f`) in the pipeline. If `f`
produces `Left` the whole pipeline short-circuits. This models
"tap-with-validation" — assert something about the current value and abort
on failure without losing the value on success.

run (chainFirst (x => x > 0 ? of ('ok') : lift (E.left ('non-positive'))) (of (5))) (0)
// => right([5, 0])
run (chainFirst (x => x > 0 ? of ('ok') : lift (E.left ('non-positive'))) (of (-1))) (0)
// => left('non-positive')

chainRec :: ((a -> Step, b -> Step, a) -> StateT s e Step) -> a -> StateT s e b

Provides stack-safe tail recursion for `StateT` by implementing the Fantasy
Land `ChainRec` contract. Instead of building up nested `chain` calls (which
would blow the stack for large inputs), the iteration runs in a JavaScript
`while` loop driven by `next`/`done` signals. Use this for loops over large
collections or indefinitely repeating stateful computations.

const sumTo = chainRec ((next, done, n) =>
  n <= 0 ? of (done (0)) : andThen (of (next (n - 1))) (modify (s => s + n))
) (5)
run (sumTo) (0)
// => right([0, 15])

sequence :: Array (StateT s e a) -> StateT s e (Array a)

Executes an array of `StateT` actions one by one, threading state through
all of them and collecting each result value into an array. Short-circuits on
the first failure, making it suitable for pipelines where every step must
succeed — such as validating and processing a batch of inputs with shared state.

run (sequence ([of (1), of (2), of (3)])) (0)
// => right([[1, 2, 3], 0])
run (sequence ([of (1), lift (E.left ('oops')), of (3)])) (0)
// => left('oops')

traverse :: (a -> StateT s e b) -> Array a -> StateT s e (Array b)

Maps a function `f` over an array of values, turning each into a `StateT`
action, then executes them in sequence while threading state through each
step. Equivalent to `sequence(xs.map(f))` but reads more clearly at the call
site. The go-to combinator for processing every element of a list with the
same potentially-failing stateful operation.

run (traverse (x => of (x * 2)) ([1, 2, 3])) (0)
// => right([[2, 4, 6], 0])
run (traverse (x => x > 0 ? of (x) : lift (E.left ('negative'))) ([1, -2, 3])) (0)
// => left('negative')

lift2 :: (a -> b -> c) -> StateT s e a -> StateT s e b -> StateT s e c

Combines two independent `StateT` actions by running them in sequence and
passing their values to a curried binary function `f`. This is a convenience
around `ap(map(f)(fa))(fb)` and avoids a `chain` plus manual tuple
unpacking when you simply want to merge two results. Short-circuits if either
action fails.

run (lift2 (a => b => a + b) (of (10)) (of (32))) (0)
// => right([42, 0])
run (lift2 (a => b => `${a} ${b}`) (of ('hello')) (of ('world'))) ({})
// => right(['hello world', {}])

String

String comparison, concatenation, and utility functions.

isString :: a -> Boolean

Returns `true` for both string primitives and `String` wrapper objects. Use it as a
type guard before string operations to avoid runtime errors when working with
mixed-type data, or pass it directly to `filter` to extract strings from a
heterogeneous array.

isString ('hi')
// => true
isString (42)
// => false
isString (null)
// => false

equals :: String -> String -> Boolean

Returns `true` only when both arguments are strings with identical content
(case-sensitive, no coercion). Unlike loose equality, this never coerces non-string
values, making it safe to use as a strict comparator in lookup tables or
deduplication logic.

equals ('a') ('a')
// => true
equals ('A') ('a')
// => false
equals ('hi') (42)
// => false

lte :: String -> String -> Boolean

Returns `true` when `a` is lexicographically less than or equal to `b`, using
JavaScript's built-in Unicode code-point order. This is useful for sorting or
ordering strings in pipelines, and serves as the foundation for `lt`, `gte`, and `gt`.

lte ('a') ('b')
// => true
lte ('b') ('b')
// => true
lte ('c') ('b')
// => false

lt :: String -> String -> Boolean

Returns `true` when `a` is lexicographically strictly less than `b`. Being curried,
it can be partially applied to produce a reusable comparator for use in conditional
guards or pipeline-based sorting logic.

lt ('a') ('b')
// => true
lt ('b') ('b')
// => false

gte :: String -> String -> Boolean

Returns `true` when `a` is lexicographically greater than or equal to `b`, derived
from `lte` by flipping its arguments. Useful for enforcing lower-bound constraints
on string values in validation pipelines.

gte ('b') ('a')
// => true
gte ('b') ('b')
// => true
gte ('a') ('b')
// => false

gt :: String -> String -> Boolean

Returns `true` when `a` is lexicographically strictly greater than `b`. The curried
form can be partially applied to produce a reusable "greater than this value"
predicate for `filter` or conditional logic.

gt ('b') ('a')
// => true
gt ('a') ('b')
// => false

min :: String -> String -> String

Returns the lexicographically smaller of two strings. Useful for computing lower
bounds or finding the alphabetically first string in a `reduce` pipeline without
importing a separate sort utility.

min ('a') ('b')
// => 'a'
min ('z') ('m')
// => 'm'

max :: String -> String -> String

Returns the lexicographically larger of two strings. Useful for computing upper
bounds or finding the alphabetically last string in a `reduce` pipeline without
importing a separate sort utility.

max ('a') ('b')
// => 'b'
max ('z') ('m')
// => 'z'

clamp :: String -> String -> String -> String

Constrains a string to the lexicographic range `[lo, hi]`, returning `lo` if the
string is below the range and `hi` if it is above. Useful when you need to enforce
alphabetical bounds — for example clamping a user-supplied sort key to a valid range.

clamp ('b') ('d') ('e')
// => 'd'
clamp ('b') ('d') ('a')
// => 'b'
clamp ('b') ('d') ('c')
// => 'c'

concat :: String -> String -> String

Concatenates two strings, returning `''` if either argument is not a valid string.
This safe default avoids runtime exceptions in pipelines that may receive `null` or
non-string values. For combining many strings, prefer `joinWith` or `unwords`.

concat ('foo') ('bar')
// => 'foobar'
concat ('hello, ') ('world')
// => 'hello, world'

empty :: String — the identity element for concat.

size :: String -> Integer

Returns the number of UTF-16 code units in the string, equivalent to `.length`.
Note that characters outside the Basic Multilingual Plane (e.g. most emoji) count as
2 units — use `chars` if you need a count of Unicode code points. Returns `0` for
non-string input instead of throwing.

size ('hello')
// => 5
size ('')
// => 0

includes :: String -> String -> Boolean

Returns `true` when `s` contains `needle` as a substring. The curried form —
`includes ('keyword')` — produces a ready-made predicate that can be passed directly
to `filter` to select matching strings from an array without wrapping in a lambda.

includes ('ell') ('hello')
// => true
includes ('xyz') ('hello')
// => false

startsWith :: String -> String -> Boolean

Returns `true` when `s` begins with the given prefix. The curried form is useful as a
predicate in `filter` or as a conditional guard in routing logic — for example,
`filter (startsWith ('/api'))` selects all API route paths from an array.

startsWith ('he') ('hello')
// => true
startsWith ('lo') ('hello')
// => false

endsWith :: String -> String -> Boolean

Returns `true` when `s` ends with the given suffix. Use the curried form in `filter`
to select file names by extension (e.g. `filter (endsWith ('.ts'))`) or to validate
that strings conform to a required suffix.

endsWith ('lo') ('hello')
// => true
endsWith ('he') ('hello')
// => false

replace :: String -> String -> String -> String

Replaces the *first* occurrence of `search` (a string or regex) within `s` with
`replacement`. The triple-curried form composes naturally in `pipe`; for example,
`replace (/foo/) ('bar')` can be mapped over an array of strings to fix the first
occurrence in each. Use `replaceAll` when you need global substitution.

replace ('l') ('r') ('hello')
// => 'herlo'
replace (/\d+/) ('NUM') ('item 42 and 7')
// => 'item NUM and 7'

replaceAll :: String -> String -> String -> String

Replaces *every* occurrence of `search` (a string or global regex) within `s` with
`replacement`. Prefer this over `replace` when you need global substitution without
manually adding the `g` flag to a regex. The triple-curried form makes it easy to
map a single substitution over an array of strings.

replaceAll ('l') ('r') ('hello')
// => 'herro'
replaceAll (' ') ('_') ('hello world foo')
// => 'hello_world_foo'

padStart :: Integer -> String -> String -> String

Pads the beginning of `s` to `len` total characters using `padStr` as fill. This is
the canonical way to zero-pad numbers for display (e.g. hours or days in a time
string) without mutating the original string. If `s` is already longer than `len`,
it is returned unchanged.

padStart (5) ('0') ('42')
// => '00042'
padStart (4) (' ') ('hi')
// => '  hi'

padEnd :: Integer -> String -> String -> String

Pads the end of `s` to `len` total characters using `padStr` as fill. Useful for
fixed-width columns in plain-text tables or for left-aligning text in tabular output.
If `s` is already longer than `len`, it is returned unchanged.

padEnd (5) ('0') ('42')
// => '42000'
padEnd (4) (' ') ('hi')
// => 'hi  '

repeat :: Integer -> String -> String

Returns `s` repeated `n` times, or an empty string for `n = 0`. This lets you
programmatically build separators, padding strings, or repetitive patterns without
manual loops. Returns an empty string for invalid inputs (negative `n` or
non-string `s`).

repeat (3) ('ab')
// => 'ababab'
repeat (0) ('ab')
// => ''

toUpper :: String -> String

Converts all characters of `s` to upper case using the default locale. This is a
lossy operation for some Unicode characters — the result may not round-trip through
`toLower`. Use in normalisation pipelines or when preparing strings for
case-insensitive display.

toUpper ('hello')
// => 'HELLO'
toUpper ('Héllo')
// => 'HÉLLO'

toLower :: String -> String

Converts all characters of `s` to lower case using the default locale. Use it in
normalisation pipelines — for example, compose `toLower` with `equals` to perform a
case-insensitive comparison, or apply it before sorting to achieve consistent ordering.

toLower ('HELLO')
// => 'hello'
toLower ('FooBAR')
// => 'foobar'

trim :: String -> String

Removes leading and trailing whitespace from `s`, including spaces, tabs, and
newlines. It is essential in input-handling pipelines where user-supplied strings
must be normalised before validation or storage — compose `trim` early in a `pipe`
to ensure downstream functions receive clean input.

trim ('  hi  ')
// => 'hi'
trim ('\t hello\n')
// => 'hello'

stripPrefix :: String -> String -> Maybe String

Returns `Just` the string with the prefix removed if `s` starts with it, otherwise
`Nothing`. This is safer than slicing by a fixed offset because it explicitly
documents the expected prefix, and the `Maybe` return type forces callers to handle
the no-match case using `Maybe`-aware combinators such as `getOrElse` or `chain`.

stripPrefix ('foo') ('foobar')
// => just('bar')
stripPrefix ('baz') ('foobar')
// => nothing()

stripSuffix :: String -> String -> Maybe String

Returns `Just` the string with the suffix removed if `s` ends with it, otherwise
`Nothing`. Like `stripPrefix`, the `Maybe` return type makes it composable with
`Maybe`-aware utilities — for example, chain it with a parser to safely strip and
process a known file extension.

stripSuffix ('bar') ('foobar')
// => just('foo')
stripSuffix ('.ts') ('index.ts')
// => just('index')
stripSuffix ('.js') ('index.ts')
// => nothing()

words :: String -> Array String

Splits a string on any run of whitespace, ignoring leading and trailing empty tokens.
This mirrors Haskell's `words` and is more robust than `split (' ')` when input
contains multiple consecutive spaces or surrounding whitespace.

words ('  foo bar  ')
// => ['foo', 'bar']
words ('one  two\tthree')
// => ['one', 'two', 'three']

unwords :: Array String -> String

Joins an array of strings with a single space between each element. It is the
inverse of `words` and is useful for reassembling tokenised words into a
human-readable string after filtering or transforming individual tokens.

unwords (['foo', 'bar'])
// => 'foo bar'
unwords (['hello', 'world', '!'])
// => 'hello world !'

lines :: String -> Array String

Splits a string into lines, correctly handling `\n`, `\r\n`, and `\r` as line
endings. The empty string yields `[]`, avoiding the spurious empty-token pitfall of
a naive `split ('\n')`. Use this for parsing multi-line text input or reading file
contents line by line.

lines ('a\nb')
// => ['a', 'b']
lines ('a\r\nb\rc')
// => ['a', 'b', 'c']
lines ('')
// => []

unlines :: Array String -> String

Joins an array of strings into a single string by appending a newline `'\n'` after
each element. This mirrors Haskell's `unlines` — note that the result always ends
with a newline, which is the POSIX convention for text files.

unlines (['a', 'b'])
// => 'a\nb\n'
unlines (['first', 'second', 'third'])
// => 'first\nsecond\nthird\n'

splitOn :: String -> String -> Array String

Splits `s` on every occurrence of the separator string `sep`. Unlike
`String.prototype.split`, both arguments are curried, making `splitOn (',')` a
reusable CSV-splitter that can be mapped over an array of raw strings in a pipeline
without writing a lambda.

splitOn (',') ('a,b,c')
// => ['a', 'b', 'c']
splitOn ('::') ('foo::bar::baz')
// => ['foo', 'bar', 'baz']

splitOnRegex :: RegExp -> String -> Array String

Splits `s` using a regex pattern that must carry the `g` flag. Unlike the native
method, this implementation safely resets `lastIndex` before and after each call so
stateful regexes do not corrupt subsequent uses. Use this instead of `splitOn` when
your delimiter has variable form and needs to be described by a pattern.

splitOnRegex (/,/g) ('a,b,c')
// => ['a', 'b', 'c']
splitOnRegex (/,\s+/g) ('a, b, c')
// => ['a', 'b', 'c']

joinWith :: String -> Array String -> String

Joins an array of strings with the given separator between elements. The curried
form — `joinWith (', ')` — is a pipeline-ready aggregator that pairs naturally with
`splitOn` to transform and reassemble delimited text, or with `words` and `unwords`
for whitespace-separated strings.

joinWith ('-') (['a', 'b', 'c'])
// => 'a-b-c'
joinWith (', ') (['red', 'green', 'blue'])
// => 'red, green, blue'

slice :: Integer -> Integer -> String -> String

Extracts a substring from index `from` (inclusive) to `to` (exclusive), supporting
negative indices that count from the end of the string. This safe wrapper returns an
empty string for non-string input rather than throwing, making it suitable for use in
pipelines over unvalidated data. The triple-curried form lets you fix the indices
once and apply the slice to many strings.

slice (1) (3) ('hello')
// => 'el'
slice (0) (5) ('hello')
// => 'hello'
slice (-3) (-1) ('hello')
// => 'll'
slice (2) (2) ('hello')
// => ''

at :: Integer -> String -> Maybe String

Returns `Just` the character at position `i`, or `Nothing` for out-of-bounds indices.
Supports negative indices (−1 is the last character). The `Maybe` return type forces
callers to handle the absent case explicitly, rather than silently receiving
`undefined` as the native `charAt` does for invalid indices.

at (0) ('hello')
// => just('h')
at (-1) ('hello')
// => just('o')
at (9) ('hello')
// => nothing()

chars :: String -> Array String

Splits a string into an array of individual Unicode code points, correctly handling
surrogate pairs such as emoji or CJK extension characters. Unlike iterating over
`.length` indices, this uses the iterable spread `[...s]` which respects Unicode
boundaries. Use this instead of `size` when you need an accurate character count
for multibyte strings.

chars ('hello')
// => ['h', 'e', 'l', 'l', 'o']
chars ('')
// => []
chars ('hi😀')
// => ['h', 'i', '😀']

Strmap

Plain JS objects used as string maps (StrMap).

equals :: (v -> v -> Boolean) -> StrMap v -> StrMap v -> Boolean

Compares two StrMaps for equality by checking every key-value pair with the
provided value comparator. Both key sets must match exactly and all
corresponding values must satisfy `eq`. Useful for asserting that two
independently-built config objects are semantically identical after separate
derivation paths.

equals (a => b => a === b) ({ host: 'localhost', port: 8080 }) ({ host: 'localhost', port: 8080 })
// => true
equals (a => b => a === b) ({ role: 'admin' }) ({ role: 'user' })
// => false
equals (a => b => a === b) ({ a: 1 }) ({ a: 1, b: 2 })
// => false

lte :: (v -> v -> Boolean) -> StrMap v -> StrMap v -> Boolean

Compares two StrMaps lexicographically for use in sorting or ordered data
structures. Keys are compared alphabetically first; when a key appears in
both maps its values are then compared with the provided comparator. Returns
`true` when `a` should sort before or at the same position as `b`.

lte (a => b => a <= b) ({ score: 10 }) ({ score: 20 })
// => true
lte (a => b => a <= b) ({ score: 20 }) ({ score: 10 })
// => false
lte (a => b => a <= b) ({ a: 1 }) ({ b: 1 })
// => true

concat :: StrMap v -> StrMap v -> StrMap v

Merges two StrMaps into one, with the right-hand map winning on duplicate
keys. This is the Semigroup instance for StrMap and models an "override"
pattern — pass your base config as `a` and a set of overrides as `b` to get
a merged result where the overrides take precedence over the base.

concat ({ host: 'localhost', port: 3000 }) ({ port: 8080, debug: true })
// => { host: 'localhost', port: 8080, debug: true }
concat ({ theme: 'light' }) ({ theme: 'dark' })
// => { theme: 'dark' }

empty :: () -> StrMap v

Returns a fresh empty StrMap — the Monoid identity element for `concat`.
Concatenating any map with `empty()` leaves it unchanged, making it the
safe starting value when building a map by folding over a list of entries.

empty ()
// => {}

singleton :: String -> a -> StrMap a

Constructs a StrMap containing exactly one key-value pair. Useful when you
need to lift a single configuration entry or lookup-table item into map form
before merging it with a larger StrMap via `concat` or `alt`.

singleton ('apiUrl') ('https://api.example.com')
// => { apiUrl: 'https://api.example.com' }
singleton ('retries') (3)
// => { retries: 3 }

fromPairs :: Array [String, a] -> StrMap a

Builds a StrMap from an array of `[key, value]` pairs — last occurrence wins
on duplicate keys. Handy for converting tabular data (CSV rows, API key-value
responses) into a fast-lookup structure, or for reconstructing a map from its
`pairs()` output.

fromPairs ([['name', 'Alice'], ['role', 'admin']])
// => { name: 'Alice', role: 'admin' }
fromPairs ([['color', 'blue'], ['color', 'red']])
// => { color: 'red' }

size :: StrMap v -> Integer

Returns the number of own enumerable keys in the map. Useful for checking
whether a config or lookup table is empty, or for asserting that a required
set of keys was fully populated after a batch operation.

size ({ host: 'localhost', port: 8080, debug: true })
// => 3
size ({})
// => 0

all :: (v -> Boolean) -> StrMap v -> Boolean

Returns `true` only when every value in the map satisfies the predicate.
Use this to validate that all entries in a config object meet a constraint
— for example, confirming that every feature flag is a boolean, or that no
numeric setting is negative.

all (v => typeof v === 'number') ({ x: 1, y: 2, z: 3 })
// => true
all (v => v > 0) ({ pass: 10, fail: -1 })
// => false

any :: (v -> Boolean) -> StrMap v -> Boolean

Returns `true` when at least one value in the map satisfies the predicate.
Handy for quickly checking whether a config or permission map contains at
least one entry of a certain kind — e.g. whether any feature flag is enabled
or any role grants write access.

any (v => v === true) ({ featureA: false, featureB: true })
// => true
any (v => v > 100) ({ x: 1, y: 2 })
// => false

none :: (v -> Boolean) -> StrMap v -> Boolean

Returns `true` when none of the values in the map satisfy the predicate —
the logical complement of `any`. Use it for guard checks such as confirming
that no required field is `null` or that no error code has been set before
proceeding with a downstream operation.

none (v => v === null) ({ host: 'localhost', port: 8080 })
// => true
none (v => v > 0) ({ a: 0, b: -1 })
// => true
none (v => v > 0) ({ a: 0, b: 1 })
// => false

elem :: (v -> v -> Boolean) -> v -> StrMap v -> Boolean

Checks whether a given value exists anywhere in the map using the provided
equality function. Useful for reverse-lookup scenarios — for example,
detecting whether a username already appears as a value in a role-to-user
map before assigning it to a new role.

elem (a => b => a === b) ('alice') ({ admin: 'alice', viewer: 'bob' })
// => true
elem (a => b => a === b) ('charlie') ({ admin: 'alice', viewer: 'bob' })
// => false

lookup :: String -> StrMap a -> Maybe a

Safely retrieves a value by key, returning `Just(value)` when the key
exists and `Nothing` when it does not. This avoids the silent `undefined`
from direct bracket access and integrates naturally into Maybe-based
pipelines where missing keys must be handled explicitly.

lookup ('host') ({ host: 'localhost', port: 8080 })
// => just('localhost')
lookup ('timeout') ({ host: 'localhost', port: 8080 })
// => nothing()
lookup ('admin') ({ admin: 'alice', viewer: 'bob' })
// => just('alice')

insert :: String -> a -> StrMap a -> StrMap a

Returns a new StrMap with the given key set to the given value, leaving the
original map unchanged. If the key already exists its value is replaced. Use
this to build configs immutably or to update a single field without mutating
shared state.

insert ('port') (9000) ({ host: 'localhost', port: 3000 })
// => { host: 'localhost', port: 9000 }
insert ('debug') (true) ({ host: 'localhost' })
// => { host: 'localhost', debug: true }

remove :: String -> StrMap a -> StrMap a

Returns a new StrMap with the specified key removed, leaving all other
entries intact and the original map unchanged. Use this to strip sensitive
fields — such as passwords or tokens — from a record before serializing
or logging it.

remove ('password') ({ username: 'alice', password: 's3cr3t', role: 'admin' })
// => { username: 'alice', role: 'admin' }
remove ('missing') ({ a: 1 })
// => { a: 1 }

map :: (a -> b) -> StrMap a -> StrMap b

Transforms every value in the StrMap by applying `f`, returning a new map
with the same keys. This is the Functor instance for StrMap and is the right
tool for batch-transforming values — for example, normalizing all config
strings to lowercase or converting raw API counts to percentages.

map (v => v.toUpperCase ()) ({ firstName: 'alice', city: 'berlin' })
// => { firstName: 'ALICE', city: 'BERLIN' }
map (v => v * 1.1) ({ base: 100, bonus: 50 })
// => { base: 110, bonus: 55 }

mapWithKey :: (String -> a -> b) -> StrMap a -> StrMap b

Like `map`, but the transformation function also receives the key. This lets
you produce values that encode information about their own field — for
example, annotating each config entry with its field name for debugging, or
building labelled audit records from a data map.

mapWithKey (k => v => `${k}: ${v}`) ({ host: 'localhost', port: '8080' })
// => { host: 'host: localhost', port: 'port: 8080' }
mapWithKey (k => v => ({ field: k, value: v })) ({ name: 'Alice' })
// => { name: { field: 'name', value: 'Alice' } }

filter :: (v -> Boolean) -> StrMap v -> StrMap v

Returns a new StrMap containing only the entries whose value satisfies the
predicate. Use it to strip unwanted or invalid fields from a config or API
response — for example, removing all `null` values or retaining only the
numeric settings.

filter (v => v !== null) ({ host: 'localhost', timeout: null, port: 8080 })
// => { host: 'localhost', port: 8080 }
filter (v => typeof v === 'number') ({ retries: 3, label: 'prod', timeout: 5000 })
// => { retries: 3, timeout: 5000 }

filterWithKey :: (String -> v -> Boolean) -> StrMap v -> StrMap v

Like `filter`, but the predicate also receives the key — allowing decisions
that depend on both the field name and its value. Useful when the same value
is valid for some keys but not others, or when you want to exclude known
internal fields by name regardless of their value.

filterWithKey (k => v => !k.startsWith ('_') && v !== null) ({ name: 'Alice', _internal: 'x', age: null })
// => { name: 'Alice' }
filterWithKey (k => v => k === 'port' || v > 0) ({ port: 0, retries: 3, timeout: -1 })
// => { port: 0, retries: 3 }

reject :: (v -> Boolean) -> StrMap v -> StrMap v

Returns a new StrMap omitting every entry whose value satisfies the
predicate — the complement of `filter`. Use it to strip known-bad values
from a map, such as removing all falsy fields before merging into a defaults
object, or discarding disabled feature flags.

reject (v => v === null || v === undefined) ({ host: 'localhost', debug: null, port: 8080 })
// => { host: 'localhost', port: 8080 }
reject (v => v === false) ({ featureA: true, featureB: false, featureC: true })
// => { featureA: true, featureC: true }

ap :: StrMap (a -> b) -> StrMap a -> StrMap b

Applies a map of functions to a map of values, pairing them by key. Only
keys present in both maps appear in the result. This enables structured,
field-level transformations where each field has its own dedicated function
— useful for validating or normalizing individual fields of a record.

ap ({ score: x => x * 2, label: s => s.trim () }) ({ score: 5, label: '  hi  ', extra: 1 })
// => { score: 10, label: 'hi' }
ap ({ age: n => n + 1 }) ({ name: 'Alice', age: 30 })
// => { age: 31 }

alt :: StrMap v -> StrMap v -> StrMap v

Merges two StrMaps with the *left* map winning on duplicate keys — the
opposite of `concat`. This models a "fill in defaults" pattern: pass your
partially-set config as `a` and a full defaults map as `b`; the result
preserves all your explicit values while filling gaps from defaults.

alt ({ port: 9000 }) ({ host: 'localhost', port: 3000, debug: false })
// => { port: 9000, host: 'localhost', debug: false }
alt ({ theme: 'dark' }) ({ theme: 'light', lang: 'en' })
// => { theme: 'dark', lang: 'en' }

reduce :: (b -> a -> b) -> b -> StrMap a -> b

Folds all values of the StrMap into a single accumulated result, visiting
entries in sorted key order. Sorted traversal guarantees a deterministic
result regardless of insertion order. Use it to compute aggregates like
totals, averages, or string summaries from a config or frequency map.

reduce (acc => v => acc + v) (0) ({ views: 120, clicks: 30, signups: 5 })
// => 155
reduce (acc => v => acc.concat ([v])) ([]) ({ b: 2, a: 1 })
// => [1, 2]

foldWithKey :: (b -> String -> a -> b) -> b -> StrMap a -> b

Folds every entry (key + value) into a single result, visiting entries in
sorted key order. The key is available to the accumulator so you can build
structured outputs — query strings, serialized representations, or an
inverted lookup table where values become the new keys.

foldWithKey (acc => k => v => `${acc}${k}=${v}&`) ('') ({ lang: 'en', theme: 'dark' })
// => 'lang=en&theme=dark&'
foldWithKey (acc => k => v => ({ ...acc, [v]: k })) ({}) ({ a: 'x', b: 'y' })
// => { x: 'a', y: 'b' }

traverse :: (b -> f b) -> (f (a->b) -> f a -> f b) -> ((a->b) -> f a -> f b) -> (v -> f b) -> StrMap v -> f (StrMap b)

Runs each value through an effectful function `f` and reassembles the
results into a StrMap inside the target applicative. Provide the
applicative's `of`, `ap`, and `map` explicitly so this function remains
dependency-free and works with any applicative — Array for non-determinism,
Maybe for partial functions, or Either for validated transformations.

const apOf  = Array.of
const apAp  = ff => fa => ff.flatMap (f => fa.map (f))
const apMap = f => fa => fa.map (f)
traverse (apOf) (apAp) (apMap) (v => [v, v + 1]) ({ x: 10 })
// => [{ x: 10 }, { x: 11 }]

keys :: StrMap a -> Array String

Returns an array of all own enumerable keys in insertion order. Use this
when you need to iterate, display, or validate the field names of a config
object without caring about the associated values.

keys ({ host: 'localhost', port: 8080, debug: true })
// => ['host', 'port', 'debug']
keys ({})
// => []

values :: StrMap a -> Array a

Returns an array of all own enumerable values in insertion order, discarding
the keys. Useful when you only care about the data — for instance, summing
numeric values across a frequency map or passing all config values to a
validation function.

values ({ alice: 42, bob: 17, carol: 99 })
// => [42, 17, 99]
values ({ active: true, banned: false })
// => [true, false]

pairs :: StrMap a -> Array [String, a]

Returns all own enumerable entries as `[key, value]` pairs in insertion
order. This is the inverse of `fromPairs` and is handy for serializing a
map to a format that requires explicit key-value pairs, such as URL query
parameters or CSV rows.

pairs ({ name: 'Alice', role: 'admin' })
// => [['name', 'Alice'], ['role', 'admin']]
pairs ({ a: 1, b: 2, c: 3 })
// => [['a', 1], ['b', 2], ['c', 3]]

These

The These type (This a | That b | Both a b).

These fills the gap between Maybe (one thing or nothing) and Either (one
thing or another). These can hold *both* values simultaneously, making it
the right tool for:
- Partial successes with warnings (Both warnings result)
- Non-destructive merging of two structures
- Annotated computations

These a b = This a | That b | Both a b

Semigroup / Monoid laws hold when both a and b are Semigroups.
The Functor / Apply instances operate on the b side (like Either).
The a side must be a Semigroup for ap / chain to accumulate it.

this_ :: a -> These a b

Constructs a `This` — the left-only variant of `These a b`, holding only the `a` value.
The trailing underscore avoids collision with JavaScript's reserved `this` keyword.
Use this constructor when you have a warning or legacy value but no corresponding right
value yet — for example, during a data migration where only the old-format record exists.

this_ ('legacy-only')
// => { tag: 'this', this: 'legacy-only' }
this_ (['missing email'])
// => { tag: 'this', this: ['missing email'] }

that :: b -> These a b

Constructs a `That` — the right-only variant of `These a b`, holding only the `b` value.
This mirrors `Right` in `Either` and represents a clean result with no annotation.
Unlike `Either`, switching to `Both` later does not require changing the constructor —
you can accumulate a left value via `concat` without discarding the right.

that (42)
// => { tag: 'that', that: 42 }
that ({ id: 7, name: 'Alice' })
// => { tag: 'that', that: { id: 7, name: 'Alice' } }

both :: a -> b -> These a b

Constructs a `Both` — the case unique to `These`, holding a left `a` and a right `b` simultaneously.
This is what separates `These` from `Either`: a computation can succeed while still carrying
a warning, annotation, or legacy value alongside the result. Use `Both` when a result is valid
but comes with a note attached — for example, a migrated record that retains the original
version for audit purposes.

both ('deprecated-field-present') ({ id: 7, name: 'Alice' })
// => { tag: 'both', this: 'deprecated-field-present', that: { id: 7, name: 'Alice' } }
both (['low confidence']) (0.74)
// => { tag: 'both', this: ['low confidence'], that: 0.74 }

isThis :: These a b -> Boolean

Returns `true` when the `These` value is a `This` — carrying only a left `a`, with no right value.
Use as a guard before extracting `a`, or to skip downstream processing when no result is present.
Complements `isThat` and `isBoth` for exhaustive case analysis without the full `these` eliminator.

isThis (this_ ('warn'))
// => true
isThis (both ('warn') (1))
// => false
isThis (that (1))
// => false

isThat :: These a b -> Boolean

Returns `true` when the `These` value is a `That` — carrying only a right `b`, with no annotation.
Use this guard to confirm a clean, annotation-free result before passing it to code that
expects no left-side value, or to count "pure success" cases in a batch.

isThat (that (42))
// => true
isThat (both ('warn') (42))
// => false
isThat (this_ ('warn'))
// => false

isBoth :: These a b -> Boolean

Returns `true` when the `These` value is a `Both` — carrying a left `a` and a right `b` at once.
This is the case that makes `These` more expressive than `Either`: a successful result that
also carries an annotation. Use this guard to identify values that need special handling
for their warning or metadata component.

isBoth (both ('warn') (42))
// => true
isBoth (that (42))
// => false
isBoth (this_ ('warn'))
// => false

isThese :: a -> Boolean

Returns `true` when the value is any variant of `These` — `This`, `That`, or `Both`.
Use as a runtime membership check before calling `These`-specific operations in contexts
where the type cannot be statically verified, such as API boundary validation or
deserialisation of external data.

isThese (that (1))
// => true
isThese (this_ ('w'))
// => true
isThese (42)
// => false

hasThis :: These a b -> Boolean

Returns `true` when the `These` carries a left `a` value — either `This` or `Both`.
Use this when you need to know whether a warning or annotation is present without caring
whether a right result also exists. This lets you log or inspect the annotation separately
from the main pipeline.

hasThis (this_ ('warn'))
// => true
hasThis (both ('warn') (1))
// => true
hasThis (that (1))
// => false

hasThat :: These a b -> Boolean

Returns `true` when the `These` carries a right `b` value — either `That` or `Both`.
Use this to check whether a usable result is present before extracting it, analogous to
`isJust` for `Maybe`. Pipelines that only care about results can guard with `hasThat`
and fall through gracefully when only an annotation is present.

hasThat (that (1))
// => true
hasThat (both ('warn') (1))
// => true
hasThat (this_ ('warn'))
// => false

these :: (a -> c) -> (b -> c) -> (a -> b -> c) -> These a b -> c

The canonical eliminator for `These` — performs full case analysis, dispatching to one
of three handlers based on which constructor is present. All three cases must be handled,
so no variant is silently ignored. Use it to fold a `These` into any output type, such as
converting it to a plain object for an API response or a human-readable log entry.

these (a => `warn:${a}`) (b => b * 2) (a => b => `${a}|${b}`) (this_ ('oops'))
// => 'warn:oops'
these (a => `warn:${a}`) (b => b * 2) (a => b => `${a}|${b}`) (that (21))
// => 42
these (a => `warn:${a}`) (b => b * 2) (a => b => `${a}|${b}`) (both ('w') (21))
// => 'w|21'

getThis :: These a b -> Maybe a

Extracts the left `a` value wrapped in `Just` when present (`This` or `Both`),
and returns `Nothing` for `That`. This lifts the partial extraction into `Maybe`,
letting downstream code stay in a safe `Maybe` pipeline without explicit branching
on the `These` constructors.

getThis (this_ ('warn'))
// => just('warn')
getThis (both ('warn') (1))
// => just('warn')
getThis (that (1))
// => nothing()

getThat :: These a b -> Maybe b

Extracts the right `b` value wrapped in `Just` when present (`That` or `Both`),
and returns `Nothing` for `This`. Use this to pluck the computation result out of
a `These` and continue in a `Maybe` pipeline — any left annotation is discarded
in the process.

getThat (that (42))
// => just(42)
getThat (both ('warn') (42))
// => just(42)
getThat (this_ ('warn'))
// => nothing()

fromThis :: a -> These a b -> a

Extracts the left `a` value, falling back to `def` when none is present (pure `That`).
A safe alternative to direct property access when a sensible default is available,
removing the need for explicit `isThis` / `isBoth` branching in the calling code.

fromThis ('none') (this_ ('warn'))
// => 'warn'
fromThis ('none') (both ('warn') (1))
// => 'warn'
fromThis ('none') (that (1))
// => 'none'

fromThat :: b -> These a b -> b

Extracts the right `b` value, falling back to `def` when none is present (pure `This`).
Use this when downstream code requires an unwrapped value and a sensible default exists,
avoiding the need for a full `these` case analysis.

fromThat (0) (that (99))
// => 99
fromThat (0) (both ('warn') (99))
// => 99
fromThat (0) (this_ ('warn'))
// => 0

equals :: (a -> a -> Boolean) -> (b -> b -> Boolean) -> These a b -> These a b -> Boolean

Tests structural equality between two `These` values, using `eqA` for the left side
and `eqB` for the right. Two `These` values are equal only when they share the same
constructor tag and their respective payloads satisfy the given comparators.
A `This` and a `That` are never equal, even if they carry equivalent inner values.

equals (a => b => a === b) (a => b => a === b) (that (1)) (that (1))
// => true
equals (a => b => a === b) (a => b => a === b) (both ('w') (1)) (both ('w') (1))
// => true
equals (a => b => a === b) (a => b => a === b) (this_ ('w')) (that (1))
// => false

concat :: (a -> a -> a) -> (b -> b -> b) -> These a b -> These a b -> These a b

Combines two `These` values as a `Semigroup`, accumulating each side independently
using `concatA` for the left and `concatB` for the right. When both operands carry
the same side, those values are merged; when only one operand carries a side, that value
is kept unchanged. Warnings accumulate, results accumulate — neither is silently discarded.

This a <> This b = This (a <> b)
This a <> That y = Both a y
This a <> Both b y = Both (a <> b) y
That x <> This b = Both b x
That x <> That y = That (x <> y)
That x <> Both b y = Both b (x <> y)
Both a x <> This b = Both (a <> b) x
Both a x <> That y = Both a (x <> y)
Both a x <> Both b y = Both (a <> b) (x <> y)

const c = concat (a => b => a + b) (a => b => a + b)
c (this_ ('a')) (this_ ('b'))
// => this_('ab')
c (that (1)) (that (2))
// => that(3)
c (both ('a') (1)) (both ('b') (2))
// => both('ab')(3)

map :: (b -> c) -> These a b -> These a c

Applies `f` to the right `b` value, preserving the left `a` untouched.
`This` passes through unchanged because there is nothing to map on the right side.
This makes `These` a right-biased `Functor`, consistent with `Either` — you can chain
`map` calls across a pipeline without special-casing the annotation-only variant.

map (x => x * 2) (that (21))
// => that(42)
map (x => x * 2) (both ('warn') (21))
// => both('warn')(42)
map (x => x * 2) (this_ ('warn'))
// => this_('warn')

mapThis :: (a -> c) -> These a b -> These c b

Applies `f` to the left `a` value, leaving the right `b` side untouched.
`That` passes through unchanged because there is no `a` present to transform.
Use this to normalise warnings, remap error codes, or transform metadata independently
of the computation result.

mapThis (s => s.toUpperCase()) (this_ ('warn'))
// => this_('WARN')
mapThis (s => s.toUpperCase()) (both ('warn') (42))
// => both('WARN')(42)
mapThis (s => s.toUpperCase()) (that (42))
// => that(42)

bimap :: (a -> c) -> (b -> d) -> These a b -> These c d

Applies `f` to both the left `a` and the right `b`, transforming whichever sides are present.
This is the `Bifunctor` interface for `These`, letting you remap both the annotation and the
result in a single pass rather than composing separate `mapThis` and `map` calls.

bimap (s => s.toUpperCase()) (n => n * 2) (both ('warn') (21))
// => both('WARN')(42)
bimap (s => s.toUpperCase()) (n => n * 2) (that (21))
// => that(42)
bimap (s => s.toUpperCase()) (n => n * 2) (this_ ('warn'))
// => this_('WARN')

of :: b -> These a b

Lifts a plain value into `These` as a `That`, establishing the `Applicative` identity.
The right slot is the "computation result" slot in `These`, so `of` wraps purely there
with no annotation — analogous to `Right` in `Either` or `Just` in `Maybe`.

of (42)
// => that(42)
of ('hello')
// => that('hello')

ap :: (a -> a -> a) -> These a (b -> c) -> These a b -> These a c

Applies the function inside `tf` to the value inside `ta`, accumulating any left `a` values
with `concatA` instead of discarding them. When both sides carry a left value those values
are merged — giving `These` its annotation-accumulation semantics similar to `Validation`,
but extended to carry successful results alongside the annotations.

const apStr = ap (a => b => a + b)
apStr (that (x => x + 1)) (that (10))
// => that(11)
apStr (both ('w1') (x => x + 1)) (both ('w2') (10))
// => both('w1w2')(11)
apStr (this_ ('e1')) (this_ ('e2'))
// => this_('e1e2')

chain :: (a -> a -> a) -> (b -> These a c) -> These a b -> These a c

Monadic bind — applies `f` to the right `b` value, threading any left `a` through with `concatA`.
When the input is a `Both`, the accumulated left value is prepended to whatever left value `f`
produces, so annotations accumulate as the chain progresses. `This` short-circuits without
calling `f`, letting you escape early when there is no result to process.

const chainStr = chain (a => b => a + b)
chainStr (x => that (x + 1)) (that (2))
// => that(3)
chainStr (x => both ('w2') (x + 1)) (both ('w1') (2))
// => both('w1w2')(3)
chainStr (x => that (x + 1)) (this_ ('e'))
// => this_('e')

fold :: (b -> a -> b) -> b -> These e a -> b

Reduces the right `b` value into an accumulator using `f` and initial value `init`.
When the `These` is a pure `This` — carrying only a left annotation — there is no `b`
to fold, so `init` is returned unchanged. This mirrors `Foldable` for `Either`: only
the right side contributes to the reduction.

fold (acc => x => acc + x) (0) (that (5))
// => 5
fold (acc => x => acc + x) (0) (both ('warn') (5))
// => 5
fold (acc => x => acc + x) (0) (this_ ('warn'))
// => 0

toMaybe :: These a b -> Maybe b

Converts a `These` to `Maybe` by extracting the right `b` value, discarding any annotation.
`That` and `Both` become `Just` (the annotation in `Both` is dropped); `This` becomes `Nothing`.
This lets you drop into the `Maybe` world when only the presence of a result matters, not the
accompanying metadata.

toMaybe (that (42))
// => just(42)
toMaybe (both ('warn') (42))
// => just(42)
toMaybe (this_ ('warn'))
// => nothing()

toEither :: These a b -> Either a b

Converts a `These` to `Either` — `That` and `Both` become `Right` (carrying `b`),
and `This` becomes `Left` (carrying `a`). This conversion is lossy for `Both`: the left
annotation is discarded, so use `getThis` separately if you need to preserve it.
Useful when interfacing with code that expects the simpler `Either` type.

toEither (that (42))
// => right(42)
toEither (both ('warn') (42))
// => right(42)
toEither (this_ ('warn'))
// => left('warn')

swap :: These a b -> These b a

Exchanges the left and right positions: `This a` becomes `That a`, `That b` becomes `This b`,
and `Both a b` becomes `Both b a`. Useful when an API returns `These` with roles reversed
from what your pipeline expects, or when you want to apply right-biased operations
(`map`, `chain`) to what was originally the left side.

swap (this_ ('warn'))
// => that('warn')
swap (that (42))
// => this_(42)
swap (both ('warn') (42))
// => both(42)('warn')

Validation

The Validation applicative (Failure | Success).

Validation is like Either but its Applicative instance accumulates errors
rather than short-circuiting on the first one. This makes it the right
tool for validating data where you want all errors reported at once.

Unlike Either, Validation is NOT a Monad — chain would require
short-circuiting, which defeats the purpose.

Validation e a = Failure e | Success a

The error type e must be a Semigroup (provide a `concat` function).
Typically e = Array String, but any semigroup works.

failure :: e -> Validation e a

Constructs a Failure variant, representing one or more accumulated errors.
Unlike Either's Left, multiple Failures can be merged via `ap` because the
error type must be a Semigroup (typically `Array`) — this allows all field
errors to be collected in one pass rather than stopping at the first. Use
this wherever a validation check does not pass.

failure (['field is required'])
// => { tag: 'failure', failure: ['field is required'] }
failure (['must be at least 8 characters', 'must contain a digit'])
// => { tag: 'failure', failure: ['must be at least 8 characters', 'must contain a digit'] }

success :: a -> Validation e a

Constructs a Success variant, representing a validated value ready to be
consumed. In an applicative pipeline, Success values are passed to the
wrapped constructor via `ap`; when every field validates successfully the
final Success is produced. Equivalent to `of`.

success (42)
// => { tag: 'success', value: 42 }
success ({ name: 'Alice', age: 30 })
// => { tag: 'success', value: { name: 'Alice', age: 30 } }

isFailure :: a -> Boolean

Type guard that returns true when the value is a Failure. Use it to branch
on a Validation result without destructuring the tag directly.

isFailure (failure (['required']))
// => true
isFailure (success (1))
// => false

isSuccess :: a -> Boolean

Type guard that returns true when the value is a Success. Use it to branch
on a Validation result without destructuring the tag directly.

isSuccess (success (1))
// => true
isSuccess (failure (['required']))
// => false

isValidation :: a -> Boolean

Returns true when the value is either a Failure or a Success, acting as a
runtime membership test for the Validation type. Useful for defensive checks
in generic utilities that accept mixed inputs.

isValidation (success (1))
// => true
isValidation (failure (['err']))
// => true
isValidation (42)
// => false

fromPredicate :: (a -> Boolean) -> e -> a -> Validation e a

Lifts a boolean predicate into a Validation, turning a simple test into a
reusable validator. When the predicate holds the input is wrapped in
Success; when it fails the provided error value is wrapped in Failure. This
is the primary way to create primitive validators that can later be combined
with `ap` to validate multiple fields at once.

fromPredicate (x => x >= 18) (['must be 18 or older']) (16)
// => failure(['must be 18 or older'])
fromPredicate (x => x >= 18) (['must be 18 or older']) (21)
// => success(21)
fromPredicate (s => s.length >= 8) (['password too short']) ('secret')
// => failure(['password too short'])

fromNullable :: e -> a -> Validation e a

Turns a possibly-null or possibly-undefined value into a Validation, using
the provided error when the value is absent. A convenient shorthand for
required-field checks in form or API validation pipelines where a missing
value should immediately produce an error.

fromNullable (['email is required']) (null)
// => failure(['email is required'])
fromNullable (['email is required']) (undefined)
// => failure(['email is required'])
fromNullable (['email is required']) ('user@example.com')
// => success('user@example.com')

validation :: (e -> b) -> (a -> b) -> Validation e a -> b

Case analysis — the canonical way to extract a final value from a
Validation without inspecting the tag directly. Both branches must return
the same type, making it the natural last step in a validation pipeline
where errors are formatted for display and the success value is consumed.

validation (es => es.join (', ')) (x => `Hello, ${x}`) (success ('Alice'))
// => 'Hello, Alice'
validation (es => es.join (', ')) (x => `Hello, ${x}`) (failure (['name required', 'too short']))
// => 'name required, too short'

map :: (a -> b) -> Validation e a -> Validation e b

Applies a transformation to the value inside a Success, leaving Failure
untouched. This is the standard Functor map, useful for post-processing a
validated value — such as normalising case or trimming whitespace — without
touching the error path.

map (s => s.trim ().toLowerCase ()) (success ('  Alice  '))
// => success('alice')
map (x => x * 2) (success (5))
// => success(10)
map (s => s.trim ().toLowerCase ()) (failure (['name required']))
// => failure(['name required'])

mapFailure :: (e -> f) -> Validation e a -> Validation f a

Applies a transformation to the errors inside a Failure, leaving Success
untouched. Use it to convert between error representations — for example,
turning an array of strings into typed Error objects, or prefixing field
names to produce structured error reports for an API response.

mapFailure (es => es.map (msg => `password: ${msg}`)) (failure (['too short', 'no digit']))
// => failure(['password: too short', 'password: no digit'])
mapFailure (es => es.map (msg => new Error (msg))) (failure (['too short']))
// => failure([Error: too short])
mapFailure (es => es.map (msg => `password: ${msg}`)) (success ('s3cr3t!!'))
// => success('s3cr3t!!')

bimap :: (e -> f) -> (a -> b) -> Validation e a -> Validation f b

Maps both sides simultaneously: fl over errors in Failure, fr over the
value in Success. A concise alternative to calling `map` and `mapFailure`
in sequence when both branches need transformation at the same time.

bimap (es => es.length) (x => x * 2) (success (3))
// => success(6)
bimap (es => es.length) (x => x * 2) (failure (['a', 'b']))
// => failure(2)

of :: a -> Validation e a

Lifts a plain value into Validation as a Success. In applicative-style form
validation, `of` is used to wrap the data constructor before applying
validated arguments with `ap` — it is the starting point of every `ap`
chain and makes the pipeline fully point-free.

of (42)
// => success(42)
of ('Alice')
// => success('Alice')

ap :: (e -> e -> e) -> Validation e (a -> b) -> Validation e a -> Validation e b

The key operation of this module — applies a validated function to a
validated argument and accumulates errors from both sides when either or
both are Failures. This is what distinguishes Validation from Either: rather
than short-circuiting at the first error, `ap` always evaluates both
arguments and merges their error Semigroups. Build form validation by
starting with `of(constructor)` and chaining one `ap` per field so that
every error is collected in a single pass.

const apArr = ap (a => b => a.concat (b))
apArr (success (x => x * 2)) (success (5))
// => success(10)
apArr (failure (['fn error'])) (failure (['arg error']))
// => failure(['fn error', 'arg error'])
const mkUser = name => age => ({ name, age })
const vName  = fromPredicate (s => s.length > 0) (['name required']) ('')
const vAge   = fromPredicate (n => n >= 18) (['must be 18+']) (15)
apArr (apArr (of (mkUser)) (vName)) (vAge)
// => failure(['name required', 'must be 18+'])

alt :: Validation e a -> Validation e a -> Validation e a

Returns the first Success encountered, falling back to the second argument
when the first is a Failure. Unlike `ap`, errors are not accumulated here —
`alt` models choice rather than combination. Use it to provide a fallback
validator or a default value when the primary check fails.

alt (success (2)) (failure (['err']))
// => success(2)
alt (failure (['e1'])) (success (1))
// => success(1)
alt (failure (['fallback failed'])) (failure (['primary failed']))
// => failure(['fallback failed'])

fold :: (b -> a -> b) -> b -> Validation e a -> b

Reduces a Success by applying f to the accumulator and the inner value,
returning the initial accumulator unchanged for Failure. Use it as a safe
extractor in pipelines where a default answer must always be produced
regardless of whether validation passed.

fold (acc => x => acc + x) (0) (success (5))
// => 5
fold (acc => xs => acc.concat (xs)) ([]) (success (['a', 'b']))
// => ['a', 'b']
fold (acc => x => acc + x) (0) (failure (['invalid input']))
// => 0

traverse :: (b -> f b) -> ((a -> b) -> f a -> f b) -> (a -> f b) -> Validation e a -> f (Validation e b)

Sequences a Validation through an outer applicative functor, turning
`Validation e (f a)` into `f (Validation e a)`. Failure is lifted directly
into the outer functor without invoking f. The explicit `apOf` and `apMap`
arguments keep the function functor-agnostic so it works with any
applicative, including Array, Promise, or a custom type.

traverse (Array.of) (f => xs => xs.map (f)) (x => [x, x * 10]) (success (3))
// => [success(3), success(30)]
traverse (Array.of) (f => xs => xs.map (f)) (x => [x, x * 10]) (failure (['err']))
// => [failure(['err'])]

toMaybe :: Validation e a -> Maybe a

Converts a Validation to a Maybe, discarding any error information. Success
becomes Just and Failure becomes Nothing. Use this when downstream code
works with Maybe and you no longer need to distinguish between specific
error values.

toMaybe (success (42))
// => just(42)
toMaybe (failure (['invalid email']))
// => nothing()

toEither :: Validation e a -> Either e a

Converts a Validation to an Either, preserving errors in Left and the value
in Right. Use this when the validated result needs to enter a monadic Either
pipeline — after collecting all errors with `ap`, switching to Either lets
you chain further transformations that short-circuit on failure.

toEither (success (1))
// => right(1)
toEither (failure (['email invalid', 'password too short']))
// => left(['email invalid', 'password too short'])

fromEither :: Either e a -> Validation e a

Converts an Either to a Validation, mapping Right to Success and Left to
Failure. Use this to enter the Validation applicative pipeline from code
that already produces Either values — such as parsing utilities or domain
functions that return Left on error.

fromEither (right (42))
// => success(42)
fromEither (left (['not a number']))
// => failure(['not a number'])

getFailure :: Validation (Array e) a -> Array e

Extracts the error collection from a Failure, or returns an empty array for
a Success. A safe accessor that always produces an array, eliminating null
checks when feeding errors into display components, logs, or aggregators.

getFailure (failure (['required', 'too short']))
// => ['required', 'too short']
getFailure (success (1))
// => []

failures :: Array (Validation (Array e) a) -> Array e

Collects and flattens all errors from every Failure in an array of
Validations, ignoring Success values. Use it to aggregate errors from
independently validated fields into a single flat list for display or
structured logging.

failures ([success (1), failure (['required']), failure (['too short', 'no digit'])])
// => ['required', 'too short', 'no digit']
failures ([success (1), success (2)])
// => []

successes :: Array (Validation e a) -> Array a

Extracts the values from all Success elements of an array, discarding
Failures. Use it to collect only the valid results from a batch of
independent validations where partial success is acceptable — for example,
importing a list of records and keeping only the ones that parsed correctly.

successes ([success (1), failure (['err']), success (3)])
// => [1, 3]
successes ([failure (['a']), failure (['b'])])
// => []

partition :: Array (Validation e a) -> [Array a, Array e]

Splits an array of Validations into a `[successes, failures]` pair,
extracting the inner values from each side in a single pass. A more
efficient alternative to calling `successes` and `failures` separately when
both results are needed at the same time — for example, reporting errors
while processing the valid entries.

partition ([success (1), failure (['e1']), success (3), failure (['e2'])])
// => [[1, 3], [['e1'], ['e2']]]
partition ([success ('a'), success ('b')])
// => [['a', 'b'], []]

Map

Functional Map with arbitrary keys.

Unlike strmap.js (which is limited to String keys), Map supports any key
type by accepting an explicit equality function wherever key comparison
is needed. Internally the map is stored as an Array of [key, value] pairs
in insertion order — this keeps the implementation dependency-free and
transparent, while remaining correct for all key types (objects, arrays,
custom ADTs, etc.).

Map k v = Array [k, v]

All operations are immutable — every "mutating" function returns a new Map.

empty :: () -> Map k v

Creates a new empty Map with no entries — the starting point when building a
Map incrementally via `insert`. Because every operation returns a new Map,
`empty()` is safe to share as a constant initial value without risk of
accidental mutation.

empty ()
// => []

singleton :: k -> v -> Map k v

Creates a Map holding exactly one entry. Useful when you need to lift a
single key-value pair into Map context before combining it with other maps
via `union`, or when constructing small lookup tables inline. Unlike
plain objects, the key may be any type — including objects and arrays.

singleton ({ id: 1 }) ('Alice')
// => [[{ id: 1 }, 'Alice']]
singleton ('theme') ('dark')
// => [['theme', 'dark']]

fromPairs :: (k -> k -> Boolean) -> Array [k, v] -> Map k v

Builds a Map from an array of `[key, value]` pairs using an explicit
equality function to detect duplicate keys — later entries win on duplicates.
This is the primary way to bulk-load data into a Map when keys are objects,
for example loading records keyed by their ID objects from an API response.

const eqById = a => b => a.id === b.id
fromPairs (eqById) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => [[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']]
fromPairs (a => b => a === b) ([['x', 1], ['x', 2]])
// => [['x', 2]]

toPairs :: Map k v -> Array [k, v]

Returns a shallow copy of the Map's internal `[key, value]` array. Use this
to escape the Map abstraction when you need to pass entries to a plain-array
operation or serialize them for storage or transmission without going through
the full fold API.

toPairs ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => [[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']]

size :: Map k v -> Integer

Returns the number of key-value pairs currently in the Map. Because the Map
tracks uniqueness via an equality function rather than hashing, `size` is
always exact with no hash-collision corner cases.

size ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => 2
size ([])
// => 0

isEmpty :: Map k v -> Boolean

Returns `true` when the Map contains no entries. Useful as a cheap guard
before attempting a fold or lookup so you can short-circuit immediately
rather than traversing an empty structure.

isEmpty ([])
// => true
isEmpty ([[{ id: 1 }, 'Alice']])
// => false

member :: (k -> k -> Boolean) -> k -> Map k v -> Boolean

Checks whether a key exists in the Map using the provided equality function.
Because keys can be arbitrary objects, standard `===` fails on structurally
equal but distinct objects — the equality parameter makes it possible to
treat two `{ id: 1 }` values as the same key.

const eqById = a => b => a.id === b.id
member (eqById) ({ id: 2 }) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => true
member (eqById) ({ id: 9 }) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => false

notMember :: (k -> k -> Boolean) -> k -> Map k v -> Boolean

The complement of `member` — returns `true` when no entry in the Map has a
key equivalent to the given one. Use it as a guard before inserting to avoid
unintentional overwrites, or to validate that a set of IDs has no conflicts
with an existing map.

const eqById = a => b => a.id === b.id
notMember (eqById) ({ id: 9 }) ([[{ id: 1 }, 'Alice']])
// => true
notMember (eqById) ({ id: 1 }) ([[{ id: 1 }, 'Alice']])
// => false

lookup :: (k -> k -> Boolean) -> k -> Map k v -> Maybe v

Safely retrieves the value associated with a key, returning `Just(value)`
when found and `Nothing` otherwise. Using Maybe instead of `undefined`
forces callers to handle the missing-key case explicitly, preventing
null-reference bugs that are otherwise easy to introduce with object keys.

const eqById = a => b => a.id === b.id
lookup (eqById) ({ id: 1 }) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => just('Alice')
lookup (eqById) ({ id: 9 }) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => nothing()

findWithDefault :: (k -> k -> Boolean) -> v -> k -> Map k v -> v

Retrieves the value for a key if present, or falls back to a supplied
default. This is a convenient short-circuit for the common pattern
`maybe(def)(id)(lookup(eq)(key)(m))` and is useful for reading config values
that have sensible defaults when not explicitly set.

const eqId = a => b => a.id === b.id
findWithDefault (eqId) ('Anonymous') ({ id: 1 }) ([[{ id: 1 }, 'Alice']])
// => 'Alice'
findWithDefault (eqId) ('Anonymous') ({ id: 9 }) ([[{ id: 1 }, 'Alice']])
// => 'Anonymous'

insert :: (k -> k -> Boolean) -> k -> v -> Map k v -> Map k v

Adds or replaces a key-value pair in the Map, returning a new Map. Key
identity is determined by the equality function, so `{ id: 1 }` will
correctly overwrite an existing entry for an equal key even though the two
objects are not reference-equal. If the key is absent it is appended.

const eqById = a => b => a.id === b.id
insert (eqById) ({ id: 2 }) ('Bob') ([[{ id: 1 }, 'Alice']])
// => [[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']]
insert (eqById) ({ id: 1 }) ('Alicia') ([[{ id: 1 }, 'Alice']])
// => [[{ id: 1 }, 'Alicia']]

insertWith :: (k -> k -> Boolean) -> k -> v -> Map k v -> Map k v

Adds a new key-value pair only when the key does not yet exist, leaving the
Map unchanged for existing keys. This is the safe "don't overwrite"
alternative to `insert` and is useful when you want to register a default
value without clobbering something already set by the caller.

const eqById = a => b => a.id === b.id
insertWith (eqById) ({ id: 1 }) ('NewAlice') ([[{ id: 1 }, 'Alice']])
// => [[{ id: 1 }, 'Alice']]
insertWith (eqById) ({ id: 3 }) ('Carol') ([[{ id: 1 }, 'Alice']])
// => [[{ id: 1 }, 'Alice'], [{ id: 3 }, 'Carol']]

insertWithCombine :: (k -> k -> Boolean) -> (v -> v -> v) -> k -> v -> Map k v -> Map k v

Adds a key-value pair to the Map, but when the key already exists the two
values are merged using `f(newVal)(oldVal)` instead of being replaced. Ideal
for aggregation scenarios such as accumulating event counts per user ID or
merging partial records from multiple sources into one Map.

const eqById = a => b => a.id === b.id
insertWithCombine (eqById) (a => b => a + b) ({ id: 1 }) (5) ([[{ id: 1 }, 10]])
// => [[{ id: 1 }, 15]]
insertWithCombine (a => b => a === b) (a => b => a + b) ('hits') (3) ([['hits', 7]])
// => [['hits', 10]]

remove :: (k -> k -> Boolean) -> k -> Map k v -> Map k v

Deletes the entry for the given key and returns a new Map, leaving the
original unchanged. The equality function is used to find the matching key,
so structural equality works correctly — removing `{ id: 1 }` will find and
delete the entry even if the stored key is a different object instance.

const eqById = a => b => a.id === b.id
remove (eqById) ({ id: 1 }) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => [[{ id: 2 }, 'Bob']]
remove (eqById) ({ id: 9 }) ([[{ id: 1 }, 'Alice']])
// => [[{ id: 1 }, 'Alice']]

update :: (k -> k -> Boolean) -> (v -> Maybe v) -> k -> Map k v -> Map k v

Applies a function to the value at a key: `Just(newValue)` replaces the old
value, while `Nothing` removes the entry entirely. This models a conditional
update-or-delete in one step, avoiding a separate `lookup` followed by either
`insert` or `remove`.

const eq = a => b => a === b
update (eq) (v => v > 1 ? M.just (v - 1) : M.nothing ()) ('lives') ([['lives', 2]])
// => [['lives', 1]]
update (eq) (v => v > 1 ? M.just (v - 1) : M.nothing ()) ('lives') ([['lives', 1]])
// => []

upsert :: (k -> k -> Boolean) -> (v -> v) -> k -> v -> Map k v -> Map k v

Updates an existing entry using `f` when the key is present, or inserts the
given default value when the key is absent. This is the idiomatic way to
implement a "get-or-create" pattern in a functional Map without first
querying and then branching on the result.

const eq = a => b => a === b
upsert (eq) (n => n + 1) ('visits') (1) ([['visits', 4]])
// => [['visits', 5]]
upsert (eq) (n => n + 1) ('visits') (1) ([])
// => [['visits', 1]]

map :: (a -> b) -> Map k a -> Map k b

Transforms every value in the Map by applying `f`, returning a new Map with
the same keys and insertion order unchanged. This is the Functor instance for
Map and is useful when you need to process all values uniformly — for example,
converting raw scores to letter grades while keeping user-ID keys intact.

map (u => u.name) ([[{ id: 1 }, { name: 'Alice', score: 82 }], [{ id: 2 }, { name: 'Bob', score: 60 }]])
// => [[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']]
map (v => v * 2) ([['a', 1], ['b', 2]])
// => [['a', 2], ['b', 4]]

mapWithKey :: (k -> a -> b) -> Map k a -> Map k b

Like `map`, but the transformation also receives the key. This is useful
when the output needs to embed key information — for instance, annotating
each user record with its own ID, or building a reverse map from values to
keys.

mapWithKey (k => v => `user-${k.id}: ${v}`) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => [[{ id: 1 }, 'user-1: Alice'], [{ id: 2 }, 'user-2: Bob']]
mapWithKey (k => v => ({ key: k, score: v })) ([['a', 10]])
// => [['a', { key: 'a', score: 10 }]]

filter :: (v -> Boolean) -> Map k v -> Map k v

Returns a new Map retaining only the entries whose value satisfies the
predicate. Use this to narrow down a lookup table — for example, keeping
only users with an active status or entries whose scores exceed a threshold.

filter (v => v.active) ([[{ id: 1 }, { name: 'Alice', active: true }], [{ id: 2 }, { name: 'Bob', active: false }]])
// => [[{ id: 1 }, { name: 'Alice', active: true }]]
filter (v => v > 50) ([['a', 80], ['b', 30], ['c', 95]])
// => [['a', 80], ['c', 95]]

filterWithKey :: (k -> v -> Boolean) -> Map k v -> Map k v

Like `filter`, but the predicate also receives the key — useful when
retention decisions depend on both the key and the value. For example, you
could keep entries where the key's ID is within a specific range and the
value meets a quality threshold.

filterWithKey (k => v => k.id < 3 && v > 0) ([[{ id: 1 }, 5], [{ id: 2 }, -1], [{ id: 3 }, 8]])
// => [[{ id: 1 }, 5]]
filterWithKey (k => v => k !== 'internal' && v !== null) ([['name', 'Alice'], ['internal', null]])
// => [['name', 'Alice']]

filterMap :: (a -> Maybe b) -> Map k a -> Map k b

Combines `map` and `filter` in one pass: applies `f` to each value and keeps
the entry only when `f` returns `Just`. Efficient for transformations that
can fail per-entry — for example, parsing or validating each value and
silently dropping invalid ones rather than aborting the whole traversal.

filterMap (v => v > 0 ? M.just (v * 10) : M.nothing ()) ([['a', 2], ['b', -1], ['c', 5]])
// => [['a', 20], ['c', 50]]
filterMap (v => Number.isFinite (v) ? M.just (v) : M.nothing ()) ([['x', 1], ['y', NaN], ['z', 3]])
// => [['x', 1], ['z', 3]]

reduce :: (b -> v -> b) -> b -> Map k v -> b

Folds all values in the Map into a single result, traversing entries in
insertion order. Use this to compute aggregates that don't require key
information — summing scores, concatenating names, or building a flat array
from a Map of lists.

reduce (acc => v => acc + v) (0) ([['alice', 42], ['bob', 17], ['carol', 99]])
// => 158
reduce (acc => v => [...acc, v]) ([]) ([['a', 1], ['b', 2]])
// => [1, 2]

foldWithKey :: (b -> k -> v -> b) -> b -> Map k v -> b

Like `reduce`, but the folding function also receives the key at each step.
This lets you build outputs that depend on both keys and values, such as
constructing an inverted index, serializing entries to a query string, or
accumulating a StrMap from a Map with object keys.

foldWithKey (acc => k => v => acc + `${k.id}:${v} `) ('') ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => '1:Alice 2:Bob '
foldWithKey (acc => k => v => ({ ...acc, [k]: v * 2 })) ({}) ([['a', 1], ['b', 2]])
// => { a: 2, b: 4 }

union :: (k -> k -> Boolean) -> Map k v -> Map k v -> Map k v

Merges two Maps into one; when both contain an equivalent key the entry from
`b` wins (right-biased). This right-biased behaviour makes it natural for
applying a set of overrides on top of a base map while keeping all entries
from both.

const eq = a => b => a.id === b.id
union (eq) ([[{ id: 1 }, 'Alice']]) ([[{ id: 2 }, 'Bob']])
// => [[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']]
union (eq) ([[{ id: 1 }, 'Alice']]) ([[{ id: 1 }, 'Alicia'], [{ id: 2 }, 'Bob']])
// => [[{ id: 1 }, 'Alicia'], [{ id: 2 }, 'Bob']]

unionLeft :: (k -> k -> Boolean) -> Map k v -> Map k v -> Map k v

Merges two Maps with the *left* map winning on duplicate keys. Use this to
merge a partial user-supplied config over a full defaults map — the user's
explicit values are preserved while any missing keys are filled in from the
defaults.

const eq = a => b => a === b
unionLeft (eq) ([['timeout', 5000]]) ([['timeout', 30000], ['retries', 3]])
// => [['timeout', 5000], ['retries', 3]]
unionLeft (eq) ([['a', 1]]) ([['a', 9], ['b', 2]])
// => [['a', 1], ['b', 2]]

unionWith :: (k -> k -> Boolean) -> (v -> v -> v) -> Map k v -> Map k v -> Map k v

Like `union`, but instead of overwriting a duplicate key it combines the two
values using `f(aVal)(bVal)`. The right tool for merging maps that represent
counts, histograms, or partial data where both entries contribute to the
final value.

const eq = a => b => a === b
unionWith (eq) (a => b => a + b) ([['views', 10]]) ([['views', 5], ['clicks', 2]])
// => [['views', 15], ['clicks', 2]]
unionWith (eq) (a => b => Math.max (a, b)) ([['score', 80]]) ([['score', 95]])
// => [['score', 95]]

intersection :: (k -> k -> Boolean) -> Map k a -> Map k b -> Map k b

Returns a Map containing only the entries whose keys appear in *both* input
maps, taking values from `b`. Use this when `b` carries updated or enriched
data and you want to apply it only to keys that already exist in your current
map `a`, discarding any new keys from `b`.

const eq = a => b => a === b
intersection (eq) ([['a', 1], ['b', 2]]) ([['b', 99], ['c', 3]])
// => [['b', 99]]
intersection (eq) ([['x', 1]]) ([['y', 2]])
// => []

intersectionWith :: (k -> k -> Boolean) -> (a -> b -> c) -> Map k a -> Map k b -> Map k c

Like `intersection`, but instead of discarding the value from `a` it
combines both values using `f(aVal)(bVal)`. Useful when the intersection
of two maps should produce derived values — for example, computing score
deltas between a baseline and a result map.

const eq = a => b => a === b
intersectionWith (eq) (a => b => a - b) ([['alice', 100], ['bob', 70]]) ([['alice', 85], ['carol', 90]])
// => [['alice', 15]]
intersectionWith (eq) (a => b => a + b) ([['x', 1], ['y', 2]]) ([['x', 10]])
// => [['x', 11]]

difference :: (k -> k -> Boolean) -> Map k a -> Map k b -> Map k a

Returns a Map of the entries in `a` whose keys have no match in `b`. Useful
for computing what needs to be added when syncing — for example, finding all
records in a source map that are absent from a destination map.

const eq = a => b => a === b
difference (eq) ([['a', 1], ['b', 2], ['c', 3]]) ([['b', 99]])
// => [['a', 1], ['c', 3]]
difference (eq) ([['x', 1]]) ([['x', 2], ['y', 3]])
// => []

equals :: (k -> k -> Boolean) -> (v -> v -> Boolean) -> Map k v -> Map k v -> Boolean

Compares two Maps for structural equality by checking that every key-value
pair in `a` has a corresponding pair in `b` with an equivalent key and value,
and that both maps have the same size. Insertion order is irrelevant, making
this safe to use after operations that may alter order.

const eqId = a => b => a.id === b.id
const eqStr = a => b => a === b
equals (eqId) (eqStr) ([[{ id: 1 }, 'Alice']]) ([[{ id: 1 }, 'Alice']])
// => true
equals (eqId) (eqStr) ([[{ id: 1 }, 'Alice']]) ([[{ id: 1 }, 'Bob']])
// => false

keys :: Map k v -> Array k

Extracts all keys from the Map in insertion order as a plain array. Use this
when you need to iterate over the key space — for example, to render a list
of user IDs in a UI or to diff the keys of two Maps.

keys ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => [{ id: 1 }, { id: 2 }]
keys ([['a', 1], ['b', 2]])
// => ['a', 'b']

values :: Map k v -> Array v

Extracts all values from the Map in insertion order, discarding the keys.
Use this when you only need the payload — for example, collecting all records
to pass to a rendering function or computing an aggregate over every entry.

values ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => ['Alice', 'Bob']
values ([['a', 10], ['b', 20]])
// => [10, 20]

all :: (v -> Boolean) -> Map k v -> Boolean

Returns `true` only when every value in the Map satisfies the predicate. Use
it to validate a whole collection at once — for instance, confirming all
retrieved records are non-null before proceeding with a batch operation.

all (v => v !== null) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => true
all (v => v > 50) ([['a', 80], ['b', 30]])
// => false

any :: (v -> Boolean) -> Map k v -> Boolean

Returns `true` when at least one value in the Map satisfies the predicate.
Use this as a quick membership check on the value space — for example,
detecting whether any user has admin rights or whether any cached result is
stale.

any (v => v.role === 'admin') ([[{ id: 1 }, { role: 'user' }], [{ id: 2 }, { role: 'admin' }]])
// => true
any (v => v < 0) ([['a', 1], ['b', 2]])
// => false

none :: (v -> Boolean) -> Map k v -> Boolean

Returns `true` when none of the Map's values satisfy the predicate — the
complement of `any`. Use this as a guard to confirm that no entry is in an
error state before proceeding with further processing.

none (v => v === null) ([[{ id: 1 }, 'Alice'], [{ id: 2 }, 'Bob']])
// => true
none (v => v.banned) ([[{ id: 1 }, { banned: false }], [{ id: 2 }, { banned: true }]])
// => false

Set

Functional Set with arbitrary equality.

Unlike JS's native Set (which uses reference equality for objects),
this module accepts an explicit equality function wherever element
comparison is needed. Internally a Set is a plain Array of unique
elements in insertion order — dependency-free and transparent.

Set a = Array a (all elements are unique under the provided eq)

All operations are immutable — every "mutating" function returns a new Set.

empty :: () -> Set a

Creates a new empty Set with no elements — the starting point when building
a set incrementally via `insert`. Because the Set is represented as an
immutable array, `empty()` is safe to use as a shared initial value without
risk of accidental mutation.

empty ()
// => []

singleton :: a -> Set a

Creates a Set containing exactly one element. Useful for wrapping a single
value into Set context before combining it with other sets via `union` or
`insert`, or when you need a unit set to test membership invariants with
`isSubsetOf` or `intersection`.

singleton ('admin')
// => ['admin']
singleton ({ id: 1 })
// => [{ id: 1 }]

fromArray :: (a -> a -> Boolean) -> Array a -> Set a

Converts an array into a Set by deduplicating elements with the provided
equality function. Because it uses custom equality rather than reference
equality, it correctly treats two structurally equal objects as duplicates.
The first occurrence of each element is kept; later duplicates are ignored.

const eqById = a => b => a.id === b.id
fromArray (eqById) ([{ id: 1 }, { id: 2 }, { id: 1 }])
// => [{ id: 1 }, { id: 2 }]
fromArray (a => b => a === b) (['read', 'write', 'read', 'execute'])
// => ['read', 'write', 'execute']

toArray :: Set a -> Array a

Returns a shallow copy of the Set as a plain array. Use this to escape the
Set abstraction when you need to pass elements to a function that expects a
regular array, or to serialize the set for storage or display.

toArray (['read', 'write', 'execute'])
// => ['read', 'write', 'execute']

size :: Set a -> Integer

Returns the number of unique elements currently in the Set. Because
deduplication is enforced at insert time, the returned count is always
accurate — there is no possibility of phantom duplicates distorting the
result.

size (['read', 'write', 'execute'])
// => 3
size ([])
// => 0

isEmpty :: Set a -> Boolean

Returns `true` when the Set contains no elements. Handy as a cheap early
exit before traversal operations such as `reduce` or `union`, avoiding
unnecessary computation on empty sets.

isEmpty ([])
// => true
isEmpty ([{ id: 1 }])
// => false

member :: (a -> a -> Boolean) -> a -> Set a -> Boolean

Checks whether an element belongs to the Set using the provided equality
function. Unlike JavaScript's native `Set.has`, which relies on reference
equality, this correctly handles structural equality — two distinct
`{ id: 1 }` objects are treated as the same element when the equality
function compares their `id` fields.

const eqById = a => b => a.id === b.id
member (eqById) ({ id: 2 }) ([{ id: 1 }, { id: 2 }, { id: 3 }])
// => true
member (eqById) ({ id: 9 }) ([{ id: 1 }, { id: 2 }])
// => false

notMember :: (a -> a -> Boolean) -> a -> Set a -> Boolean

The complement of `member` — returns `true` when the element is absent from
the Set. Use it as a guard before insertion to avoid no-ops, or to assert
that a newly generated ID does not collide with any existing member.

const eqById = a => b => a.id === b.id
notMember (eqById) ({ id: 9 }) ([{ id: 1 }, { id: 2 }])
// => true
notMember (eqById) ({ id: 1 }) ([{ id: 1 }, { id: 2 }])
// => false

insert :: (a -> a -> Boolean) -> a -> Set a -> Set a

Adds an element to the Set if it is not already a member, returning a new
Set. If an equivalent element is already present the original Set is returned
unchanged, making repeated insertions idempotent. The equality function is
used for the duplicate check, so object-valued sets work correctly.

const eqById = a => b => a.id === b.id
insert (eqById) ({ id: 3 }) ([{ id: 1 }, { id: 2 }])
// => [{ id: 1 }, { id: 2 }, { id: 3 }]
insert (eqById) ({ id: 1 }) ([{ id: 1 }, { id: 2 }])
// => [{ id: 1 }, { id: 2 }]

remove :: (a -> a -> Boolean) -> a -> Set a -> Set a

Removes an element from the Set and returns a new Set without it. If the
element is absent the original Set is returned unchanged. The equality
function governs what counts as "the same element", so structural equality
on objects is fully supported.

const eqById = a => b => a.id === b.id
remove (eqById) ({ id: 2 }) ([{ id: 1 }, { id: 2 }, { id: 3 }])
// => [{ id: 1 }, { id: 3 }]
remove (eqById) ({ id: 9 }) ([{ id: 1 }, { id: 2 }])
// => [{ id: 1 }, { id: 2 }]

union :: (a -> a -> Boolean) -> Set a -> Set a -> Set a

Returns a Set containing every element from either input Set, with
duplicates removed. Elements from `a` are kept when both sets contain an
equivalent element (left-biased). Essential when merging permission sets,
tag lists, or any collection where membership matters more than order.

const eq = a => b => a === b
union (eq) (['read', 'write']) (['write', 'execute'])
// => ['read', 'write', 'execute']
union (a => b => a.id === b.id) ([{ id: 1 }, { id: 2 }]) ([{ id: 2 }, { id: 3 }])
// => [{ id: 1 }, { id: 2 }, { id: 3 }]

intersection :: (a -> a -> Boolean) -> Set a -> Set a -> Set a

Returns a Set of elements that exist in both input Sets, taking values from
`a`. This is the standard set-intersection and is useful for finding the
overlap between two groups — for example, identifying users who hold all
required permissions, or tags shared between two articles.

const eq = a => b => a === b
intersection (eq) (['read', 'write', 'execute']) (['write', 'execute', 'delete'])
// => ['write', 'execute']
intersection (a => b => a.id === b.id) ([{ id: 1 }, { id: 2 }]) ([{ id: 2 }, { id: 3 }])
// => [{ id: 2 }]

difference :: (a -> a -> Boolean) -> Set a -> Set a -> Set a

Returns the elements in `a` that are absent from `b` — the set-theoretic
difference `a \ b`. Useful for computing what needs to be added when syncing
two sets, or for finding which permissions a user still lacks after comparing
their granted set against the required set.

const eq = a => b => a === b
difference (eq) (['read', 'write', 'execute']) (['write'])
// => ['read', 'execute']
difference (a => b => a.id === b.id) ([{ id: 1 }, { id: 2 }, { id: 3 }]) ([{ id: 2 }])
// => [{ id: 1 }, { id: 3 }]

symmetricDifference :: (a -> a -> Boolean) -> Set a -> Set a -> Set a

Returns elements that appear in exactly one of the two Sets — those present
in `a` but not `b`, plus those in `b` but not `a`. This models the XOR of
two sets and is useful for computing "what changed" between two snapshots:
elements that were added or removed, but not those that stayed the same.

const eq = a => b => a === b
symmetricDifference (eq) (['a', 'b', 'c']) (['b', 'c', 'd'])
// => ['a', 'd']
symmetricDifference (eq) ([1, 2]) ([2, 3])
// => [1, 3]

isSubsetOf :: (a -> a -> Boolean) -> Set a -> Set a -> Boolean

Returns `true` when every element of `a` is also present in `b` (a ⊆ b).
Use this to verify that a required set of capabilities is entirely covered
by an available set — for example, checking that all mandatory permissions
are included in a user's role before granting access.

const eq = a => b => a === b
isSubsetOf (eq) (['read', 'write']) (['read', 'write', 'execute'])
// => true
isSubsetOf (eq) (['read', 'delete']) (['read', 'write'])
// => false
isSubsetOf (eq) ([]) (['read', 'write'])
// => true

equals :: (a -> a -> Boolean) -> Set a -> Set a -> Boolean

Compares two Sets for equality: returns `true` when both contain exactly the
same elements according to the equality function, regardless of insertion
order. This is more reliable than comparing array representations directly,
since order is not meaningful for sets.

const eq = a => b => a === b
equals (eq) (['read', 'write']) (['write', 'read'])
// => true
equals (eq) (['read']) (['read', 'write'])
// => false

map :: (a -> b) -> Set a -> Set b

Transforms every element of the Set by applying `f`. Because `f` may
collapse distinct elements to the same value, the result is not guaranteed
to be duplicate-free — use `mapUniq` when uniqueness must be enforced. Plain
`map` is provided for performance when you know `f` is injective.

map (x => x.toUpperCase ()) (['read', 'write', 'execute'])
// => ['READ', 'WRITE', 'EXECUTE']
map (u => u.role) ([{ id: 1, role: 'admin' }, { id: 2, role: 'user' }])
// => ['admin', 'user']

mapUniq :: (b -> b -> Boolean) -> (a -> b) -> Set a -> Set b

Like `map`, but deduplicates the result using the provided equality function
after applying `f`. Use this whenever `f` might project multiple distinct
elements to the same value — for example, extracting the `role` field from a
set of user objects where multiple users share a role.

const eq = a => b => a === b
mapUniq (eq) (u => u.role) ([{ id: 1, role: 'admin' }, { id: 2, role: 'user' }, { id: 3, role: 'admin' }])
// => ['admin', 'user']
mapUniq (eq) (x => x % 2) ([1, 2, 3, 4])
// => [1, 0]

filter :: (a -> Boolean) -> Set a -> Set a

Returns a new Set containing only the elements that satisfy the predicate.
Use this to narrow down a set — for example, retaining only active users,
filtering permissions to those in a particular category, or keeping only IDs
within a valid range.

filter (x => x.active) ([{ id: 1, active: true }, { id: 2, active: false }, { id: 3, active: true }])
// => [{ id: 1, active: true }, { id: 3, active: true }]
filter (x => x > 2) ([1, 2, 3, 4])
// => [3, 4]

reduce :: (b -> a -> b) -> b -> Set a -> b

Folds all elements of the Set into a single accumulated result, visiting
entries in insertion order. This is the primary way to compute a single value
from a set — for example, summing IDs, concatenating labels, or building a
lookup table from a set of records.

reduce (acc => x => acc + x) (0) ([1, 2, 3, 4])
// => 10
reduce (acc => x => [...acc, x.id]) ([]) ([{ id: 1 }, { id: 2 }, { id: 3 }])
// => [1, 2, 3]

all :: (a -> Boolean) -> Set a -> Boolean

Returns `true` only when all elements in the Set satisfy the predicate.
Useful for invariant checks — for example, verifying that every user in a set
has a valid email, or that all permission strings follow a naming convention
before storing them.

all (x => typeof x === 'string') (['read', 'write', 'execute'])
// => true
all (x => x.active) ([{ active: true }, { active: false }])
// => false

any :: (a -> Boolean) -> Set a -> Boolean

Returns `true` when at least one element in the Set satisfies the predicate.
Use this for quick membership-by-property checks — for example, testing
whether any user in a set is an admin, or whether any tag in a post's tag
set matches a given category.

any (x => x.role === 'admin') ([{ role: 'user' }, { role: 'admin' }])
// => true
any (x => x < 0) ([1, 2, 3])
// => false

none :: (a -> Boolean) -> Set a -> Boolean

Returns `true` when no element in the Set satisfies the predicate — the
complement of `any`. Use this as a guard to confirm that a set is free from
invalid or prohibited members before committing to further processing.

none (x => x.banned) ([{ banned: false }, { banned: false }])
// => true
none (x => x > 10) ([1, 5, 3])
// => true
none (x => x > 3) ([1, 5, 3])
// => false

find :: (a -> Boolean) -> Set a -> Maybe a

Finds and returns the first element in the Set that satisfies the predicate,
wrapped in `Just`, or `Nothing` if no element qualifies. This avoids the
need to check for `undefined` from a raw `Array.find` call and integrates
cleanly into Maybe-based pipelines.

find (x => x.role === 'admin') ([{ id: 1, role: 'user' }, { id: 2, role: 'admin' }])
// => just({ id: 2, role: 'admin' })
find (x => x > 10) ([1, 2, 3])
// => nothing()

count :: (a -> Boolean) -> Set a -> Integer

Counts how many elements in the Set satisfy the predicate. Use this when you
need a summary statistic — for example, counting how many users have admin
rights, or how many tags in a post belong to a particular category.

count (x => x.active) ([{ active: true }, { active: false }, { active: true }])
// => 2
count (x => x > 2) ([1, 2, 3, 4])
// => 2

partition :: (a -> Boolean) -> Set a -> [Set a, Set a]

Splits the Set into two arrays `[matching, nonMatching]` in a single pass
based on the predicate. More efficient than calling `filter` and `reject`
separately when you need both halves — for example, sorting users into
active and inactive groups, or separating valid records from invalid ones.

partition (x => x.active) ([{ id: 1, active: true }, { id: 2, active: false }, { id: 3, active: true }])
// => [[{ id: 1, active: true }, { id: 3, active: true }], [{ id: 2, active: false }]]
partition (x => x > 2) ([1, 2, 3, 4])
// => [[3, 4], [1, 2]]