@hecto/fp
v3.0.0
Published
Portable, performant functional programming utilities for TypeScript.
Readme
@hecto/fp
A performance-first functional standard library for TypeScript. It combines a small composition root, tree-shakeable specialist modules, explicit data types, lazy iteration, immutable collection utilities, and immutable collection utilities.
pnpm add @hecto/fpimport { pipe } from '@hecto/fp'
import * as A from '@hecto/fp/array'
import * as O from '@hecto/fp/option'
const firstActiveName = pipe(
users,
A.filter((user) => user.active),
A.map((user) => user.name),
A.head,
O.getOrElse(() => 'Nobody'),
)The root deliberately contains only sequential composition and the core Option/Result constructors and guards. specialist APIs from subpaths:
import { pipe, flow, some, ok } from '@hecto/fp'
import * as A from '@hecto/fp/array'
import * as Iter from '@hecto/fp/iter'
import * as R from '@hecto/fp/result'
import * as Optic from '@hecto/fp/optic'Root pipe, flow, and compile apply each step sequentially. This preserves
callback order, callback counts, and errors even when later steps discard a
value. Use compilePure, or import pipe/flow from @hecto/fp/fusion,
only when every callback is total, depends only on its arguments, and has no
observable side effects. Those explicit-purity forms may fuse, skip, or
reorder callback evaluation while preserving the pipeline's result.
Design contracts
- Modern ESM for Node 22+, Bun 1.3+, Deno 2+, browsers, workers, and edge runtimes.
- Ordinary
pipe,flow, andcompiledo not use runtime code generation. Explicit-puritycompilePureand thefusionsubpath may compile a fused runner withnew Function; capability detection is lazy and a sequential or hand-written fallback is used where string compilation is disabled. - Ordinary collection operations return new values. Destination reuse is
opt-in through named
*IntoAPIs. *IntoAPIs preserve the concrete destination type. Fixed tuples, heterogeneous or refined record destinations, and ambiguous mutable target unions are rejected when mutation cannot be proven sound.- Sparse arrays have dense semantics: a hole is observed as
undefined. - Callback-driven traversals in the
arraymodule snapshot the input length before the first callback; appends are not visited and truncation is observed as denseundefinedslots through the original length. - Partial operations return
Optionby default. Explicit throwing andundefinedvariants are named. - SameValueZero is the default equality for collection membership.
recordis the homogeneous dictionary API (ReadonlyRecord<A>). Closed, named object shapes, including interfaces without an index signature, belong inobject, whose operations preserve their declared shape.- Synchronous failures use
Result. Asynchronous failures and cancellation live in@hecto/asyncasTask; there is no competingAsyncResult.
Modules
Collections and iteration:
array,non-empty-array,tuplerecord,map,set,typed-array,indexediter
Data and validation:
option,result,validation,these,nullable
Standard Schema interoperability now belongs to @hecto/schema.
Algebra and functions:
eq,hash,ord,ordering,semigroup,monoid,groupfunction,guard,object,string,number,math,boolean
Composition and programs:
opticreader,state-fn,writer,recursion
The package exposes 488 dual operations. Each supports direct data-first and curried data-last calls under the same name. The complete dual-call catalogue lists both public shapes for every operation, including staged currying and optional arguments.
const firstFive = A.take(values, 5)
const alsoFirstFive = A.take(5)(values)
pipe(values, A.take(5))Performance tiers
Repository benchmarks answer current implementation questions. Their results are measurements rather than release thresholds; rerun the relevant benchmark before making a performance claim against the current source.
The package stays compatible with CSP policies that omit unsafe-eval.
Importing the public root and using ordinary composition constructs no runtime
code. Because the root also exports compilePure, its complete dependency
graph contains the optional code generator and callback source parser. Those
explicit-purity APIs detect string-compilation support on first use and fall
back when it is blocked. @hecto/compiler provides build-time lowering when
runtime code generation is unavailable.
Lazy iteration
Iter replaces the old Stream API. It is lazy, re-iterable when its source
is, closes upstream iterators on early termination, and uses Option terminals.
const values = pipe(
Iter.range(0, Infinity),
Iter.filter((value) => value % 2 === 0),
Iter.take(3),
Iter.toArray,
)Default to array. It wins 2-4x over Iter on chains that consume the
whole input, at every size measured. Reach for Iter when a chain stops
early -- find, take, head -- on an input of 1,000 elements or more:
eager loses there, and Iter wins 5-884x on those same shapes. Compile
whichever chain you've actually measured as hot (see "Compiling pipelines"
above).
For asynchronous sources, bounded concurrency, cancellation, and Task
terminals, use @hecto/async/async-iter.
