Skip to content

Getting Started

Terminal window
bun add @stopcock/fp
import { pipe, flow, A, S, N, O, R } from '@stopcock/fp'

Pass a value through functions, left to right. If you chain array operations, they get fused into single loops automatically.

type User = { name: string; active: boolean; score: number }
const leaderboard = pipe(
users,
A.filter((u: User) => u.active && u.score > 0),
A.sortBy((a: User, b: User) => b.score - a.score),
A.take(10),
A.map((u: User) => u.name),
)

filter → map fuses into a single loop. take(10) terminates early, so the loop stops after 10 results.

Same as pipe but gives you a reusable function instead of running straight away.

const activeNames = flow(
A.filter((u: User) => u.active),
A.map((u: User) => u.name),
)
activeNames(usersA)
activeNames(usersB)

pipe detects tagged array operations and fuses them into a single loop. take and find bail out early, so you’re not iterating the whole thing.

const big = Array.from({ length: 1_000_000 }, (_, i) => i)
// visits ~70 items, allocates nothing intermediate
pipe(big, A.filter(x => x % 7 === 0), A.map(x => x * 2), A.take(10))

Operations that need the whole array (sort, groupBy, uniq) break the fusion chain. See Fusion for the full picture.

Every function works both ways:

A.take(users, 5) // data-first
pipe(users, A.take(5)) // data-last
pipe(
O.fromNullable(user.email),
O.map(e => e.split('@')[1]),
O.getWithDefault('unknown'),
)
pipe(
R.tryCatch(() => JSON.parse(input)),
R.map((obj: { value: number }) => obj.value),
R.getOrElse(() => null),
)

See Option & Result for the full API, or try the Cookbook for more patterns.