fp
bun add @stopcock/fp@stopcock/fp is a focused functional-programming toolkit for TypeScript. It
provides typed data types, eager and lazy collections, algebra, optics,
validation, pattern matching, recursion schemes, and a portable pipeline
compiler without taking ownership of your application runtime.
The root is intentionally small:
import { err, flow, none, ok, pipe, some } from '@stopcock/fp'compile, compilePure, dual, and explain are not root exports. Import
them from the subpaths that name what they are: @stopcock/fp/compile,
@stopcock/fp/dual, and @stopcock/fp/fusion/debug.
Specialist APIs live in tree-shakeable subpaths:
import * as A from '@stopcock/fp/array'import * as Iter from '@stopcock/fp/iter'import * as O from '@stopcock/fp/option'import * as R from '@stopcock/fp/result'import * as V from '@stopcock/fp/validation'import * as Optic from '@stopcock/fp/optic'Everyday pipelines
Section titled “Everyday pipelines”import { pipe } from '@stopcock/fp'import * as A from '@stopcock/fp/array'
const activeNames = pipe( players, A.filter((player) => player.active), A.map((player) => player.name), A.take(10),)Every operator has one form: curried, data-last.
const doubled = A.map((value: number) => value * 2)([1, 2, 3])const alsoDoubled = pipe([1, 2, 3], A.map((value) => value * 2))Array operations are immutable by default. Explicit *Into operations write
into caller-owned storage, while view-named operations are the only APIs
allowed to alias an input.
Portable compilation
Section titled “Portable compilation”There is one runtime path: pipe, flow, compile, and compilePure are
all the same plain, left-to-right, sequential application. compile exists
so a call site can say “this is a pipeline I intend to compile” by name;
uncompiled, it behaves exactly like pipe, including callback order and
early-exit counts.
import { compile } from '@stopcock/fp/compile'
const activeNames = compile( A.filter((player: Player) => player.active), A.map((player) => player.name), A.take(10),)
activeNames(players)The actual fusion is @stopcock/fp-compiler: a build-time transform that
recognizes a pipe/flow/compile call over these operators and replaces
it with an inlined, single-pass loop, so the runtime engine above never runs
for that call at all. compilePure exists for source parity with the
compiler’s assumePure option; at runtime it is compile under a different
name.
import { explain } from '@stopcock/fp/fusion/debug'
explain( A.map((value: number) => value * 2), A.filter((value) => value > 0),) // 'sequential', always -- a call the compiler actually fused never // reaches this code to be explainedUncompiled pipe is already at or near hand-loop speed for most chains, so
you do not need the compiler to get reasonable performance. Reach for
@stopcock/fp-compiler for pipelines you’ve actually measured as hot.
Missing values, failures, and validation
Section titled “Missing values, failures, and validation”Use Option for absence, Result for fail-fast recoverable errors, and
Validation when independent checks should accumulate all errors.
import { pipe } from '@stopcock/fp'import * as O from '@stopcock/fp/option'import * as R from '@stopcock/fp/result'import * as V from '@stopcock/fp/validation'
const port = pipe( O.fromNullable(process.env.PORT), O.map(Number), O.filter((value) => Number.isInteger(value) && value > 0), O.getOrElse(() => 3000),)
const parsed = R.tryCatch( () => JSON.parse(rawConfig) as Config, (cause) => ({ type: 'InvalidJson' as const, cause }),)
const fields = V.all([ V.fromPredicate((value: string) => value.length > 0, () => 'name is required')(name), V.fromPredicate(Number.isFinite, () => 'age must be finite')(age),] as const)Asynchronous failure belongs in @stopcock/async/task; there is deliberately
no competing AsyncResult type.
Lazy sequences
Section titled “Lazy sequences”Iter is the single lazy synchronous sequence abstraction:
import { pipe } from '@stopcock/fp'import * as Iter from '@stopcock/fp/iter'
const squares = pipe( Iter.range(1, Infinity), Iter.map((value) => value * value), Iter.filter((value) => value % 2 === 0), Iter.take(5), Iter.toArray,)See Iter, Option and Result, Validation, Optics, and the complete module catalogue.