@algosail/fn
v0.1.0
Published
Function combinators. Building blocks for point-free, pipeable code.
Readme
@algosail/fn
Function combinators and the Reader monad. Building blocks for point-free, pipeable code.
Contents
pipe
pipe :: Foldable f => f (Any -> Any) -> a -> bThreads a value through a left-to-right sequence of functions. The primary composition tool.
pipe([x => x + 1, x => x * 2])(3) // => 8 (first +1, then *2)
pipe([Math.abs, Math.sqrt])(−9) // => 3
pipe([String, s => s.toUpperCase()])(42) // => '42' -> '42' (wrong, let me fix:)
// Build reusable transforms
const normalize = pipe([s => s.trim(), s => s.toLowerCase()])
normalize(' Hello ') // => 'hello'pipeK
pipeK :: (Foldable f, Flatmap m) => f (Any -> m Any) -> m a -> m bLeft-to-right Kleisli composition. Each function receives a plain value and returns a monadic value; the monad is threaded through via flatmap.
import * as M from '@algosail/maybe'
const safeSqrt = (x) => (x >= 0 ? M.just(Math.sqrt(x)) : M.nothing())
const safeLog = (x) => (x > 0 ? M.just(Math.log(x)) : M.nothing())
pipeK([safeSqrt, safeLog])(M.just(100)) // => just(log(10))
pipeK([safeSqrt, safeLog])(M.just(-1)) // => nothing()id
id :: a -> aIdentity — returns its argument unchanged. Useful as a no-op placeholder.
id(42) // => 42
id([1, 2, 3]) // => [1,2,3]const_
const_ :: a -> b -> aK combinator — always returns the first argument regardless of the second.
const_('x')('ignored') // => 'x'
[(1, 2, 3)].map(const_(0)) // => [0, 0, 0]T
T :: a -> (a -> b) -> bThrush combinator — applies an argument to a function. Useful for flipping callsite style.
T(42)((x) => x + 1) // => 43
T([1, 2, 3])((arr) => arr.length) // => 3on
on :: (b -> b -> c) -> (a -> b) -> a -> a -> cP combinator — applies a binary function after first mapping both arguments through g.
// Compare strings case-insensitively
on((a) => (b) => a === b)((s) => s.toLowerCase())('Hello')('hello') // => true
// Sum lengths
on((a) => (b) => a + b)((s) => s.length)('foo')('hello') // => 8compose
compose :: (b -> c) -> (a -> b) -> a -> cRight-to-left function composition.
compose((x) => x * 2)((x) => x + 1)(3) // => 8 (first +1, then *2)flip
flip :: (a -> b -> c) -> b -> a -> cFlips the order of the first two arguments of a curried function.
const sub = (a) => (b) => a - b
sub(1)(3) // => -2
flip(sub)(1)(3) // => 2 (3 - 1)contramap
contramap :: (b -> a) -> (a -> c) -> b -> cPre-composes a function (contravariant map). Transforms the input of a function.
const strlen = (s) => s.length
contramap((s) => s.trim())(strlen)(' hi ') // => 2promap
promap :: (a -> b) -> (c -> d) -> (b -> c) -> a -> dMaps both the input and output of a function.
// Transform input with +1, output with *2
promap((x) => x + 1)((x) => x * 2)((x) => x)(3) // => 8handleThrow
handleThrow :: ((...d) -> a) -> ((a, d) -> r) -> ((Error, d) -> r) -> (...d) -> rWraps a function so thrown errors are caught and routed to onThrow instead of propagating.
const safeParseJSON = handleThrow(
JSON.parse,
(result) => result,
(err) => null,
)
safeParseJSON('{"a":1}') // => { a: 1 }
safeParseJSON('!bad!') // => nullmap
map :: (a -> b) -> (e -> a) -> e -> bReader functor — post-composes a function. Transforms the output.
map((x) => x + 1)((x) => x * 2)(3) // => 7 (3*2=6, then +1)ap
ap :: (e -> a -> b) -> (e -> a) -> e -> bS combinator — ap(ff)(fa)(x) = ff(x)(fa(x)).
ap((e) => (x) => e + x)((e) => e * 2)(3) // => 9 (3 + 3*2)of
of :: a -> e -> aLifts a value into the Reader context (constant function).
of(42)('anything') // => 42flatmap
flatmap :: (a -> e -> b) -> (e -> a) -> e -> bReader monad bind — depends on the same environment e.
// Both functions share the environment (multiplier)
flatmap((a) => (e) => a + e)((e) => e * 2)(3) // => 9 (3*2=6, then 6+3)extend
extend :: ((e, e) -> e) -> ((e -> a) -> b) -> (e -> a) -> e -> bComonad extend for the Reader context.
chainRec
chainRec :: ((next, done, a) -> e -> Step) -> a -> e -> bStack-safe tail-recursive Reader bind. Use instead of flatmap when recursion depth is unbounded.
// Count down without stack overflow
chainRec((next, done, n) => (_) => (n <= 0 ? done(n) : next(n - 1)))(1_000_000)(
null,
) // => 0