A (Array)
Every operator is curried: A.fn(...)(arr), which is what makes it drop into
pipe(arr, A.fn(...)). Streaming operators such as map, filter,
flatMap, take, drop, takeWhile, and dropWhile can be fused into a
single loop by the separate build-time @stopcock/fp-compiler
plugin. Plain pipe at runtime never fuses on its own; it just calls each
step in order. Terminal folds, accessor terminals, and materializing
operations have different boundaries in what the compiler can fuse.
Accessors
Section titled “Accessors”head<A>(arr: readonly A[]): Option<A>last<A>(arr: readonly A[]): Option<A>first<A>(arr: readonly A[]): Option<A> // alias for headheadOrUndefined<A>(arr: readonly A[]): A | undefinedlastOrUndefined<A>(arr: readonly A[]): A | undefinedheadNonEmpty<A>(arr: readonly [A, ...A[]]): AlastNonEmpty<A>(arr: readonly [A, ...A[]]): Atail<A>(arr: readonly A[]): A[] // all except firstinit<A>(arr: readonly A[]): A[] // all except lastlength<A>(arr: A[]): numberisEmpty<A>(arr: A[]): booleanhead, last, length, and isEmpty are accessor terminals: the compiler
can treat them as the last step of a fused loop. Option-returning names are
the default; *OrUndefined is the explicit JavaScript interop escape hatch.
Transforms
Section titled “Transforms”map<A, B>(f: (a: A) => B): (arr: A[]) => B[]mapWithIndex<A, B>(f: (a: A, i: number) => B): (arr: A[]) => B[]filter<A>(pred: (a: A) => boolean): (arr: A[]) => A[]filterWithIndex<A>(pred: (a: A, i: number) => boolean): (arr: A[]) => A[]flatMap<A, B>(f: (a: A) => B[]): (arr: A[]) => B[]reduce<A, B>(f: (acc: B, a: A) => B, init: B): (arr: A[]) => BreduceRight<A, B>(f: (acc: B, a: A) => B, init: B): (arr: A[]) => Bscan<A, B>(f: (acc: B, a: A) => B, init: B): (arr: A[]) => B[]forEach<A>(f: (a: A) => void): (arr: A[]) => voidforEachWithIndex<A>(f: (a: A, i: number) => void): (arr: A[]) => voidmap, mapWithIndex, filter, filterWithIndex, and flatMap are the
streaming steps the compiler can fuse into one loop. reduce is a terminal
fold. forEach is a terminal side-effect traversal. scan returns an
intermediate array of every running value.
Ordering
Section titled “Ordering”sort(arr: number[]): number[]sortBy<A>(cmp: (a: A, b: A) => number): (arr: A[]) => A[]reverse<A>(arr: A[]): A[]Materialization boundaries: they need the whole array before they can
produce anything. sort and sortBy always run a full sort; a following
take does not get a bounded top-k shortcut.
Slicing
Section titled “Slicing”take(n: number): <A>(arr: A[]) => A[]drop(n: number): <A>(arr: A[]) => A[]takeWhile<A>(pred: (a: A) => boolean): (arr: A[]) => A[]dropWhile<A>(pred: (a: A) => boolean): (arr: A[]) => A[]chunk(n: number): <A>(arr: A[]) => A[][]slidingWindow(n: number): <A>(arr: A[]) => A[][]aperture(n: number): <A>(arr: A[]) => A[][]take, drop, takeWhile, and dropWhile are streaming steps the
compiler can fuse. take and takeWhile can stop the loop early.
Search
Section titled “Search”find<A>(pred: (a: A) => boolean): (arr: readonly A[]) => Option<A>findIndex<A>(pred: (a: A) => boolean): (arr: readonly A[]) => Option<number>findOrUndefined<A>(pred: (a: A) => boolean): (arr: readonly A[]) => A | undefinedfindIndexOrUndefined<A>(pred: (a: A) => boolean): (arr: readonly A[]) => number | undefinedevery<A>(pred: (a: A) => boolean): (arr: A[]) => booleansome<A>(pred: (a: A) => boolean): (arr: A[]) => booleanincludes<A>(value: A): (arr: A[]) => booleanfind, every, and some are terminal predicates/searches: the compiler
can run them as the last step of a fused loop and bail out early.
Deduplication
Section titled “Deduplication”uniq<A>(arr: A[]): A[]uniqBy<A, B>(f: (a: A) => B): (arr: A[]) => A[]Deduplication needs seen-value state and returns a materialized array.
Set operations
Section titled “Set operations”intersection<A>(b: A[]): (a: A[]) => A[]union<A>(b: A[]): (a: A[]) => A[]difference<A>(b: A[]): (a: A[]) => A[]symmetricDifference<A>(b: A[]): (a: A[]) => A[]Combinators
Section titled “Combinators”zip<A, B>(b: B[]): (a: A[]) => [A, B][]zipWith<A, B, C>(b: B[], f: (a: A, b: B) => C): (a: A[]) => C[]xprod<A, B>(b: B[]): (a: A[]) => [A, B][]groupBy<A>(f: (a: A) => string): (arr: readonly A[]) => Record<string, A[]>partition<A>(pred: (a: A) => boolean): (arr: A[]) => [A[], A[]]intersperse<A>(sep: A): (arr: A[]) => A[]flatten<A>(arr: A[][]): A[]transpose<A>(arr: A[][]): A[][]groupBy is a materialization boundary because it needs the whole grouped result.
Mutation (immutable)
Section titled “Mutation (immutable)”adjust<A>(index: number, f: (a: A) => A): (arr: A[]) => A[]update<A>(index: number, value: A): (arr: A[]) => A[]insert<A>(index: number, value: A): (arr: A[]) => A[]remove(index: number, count: number): <A>(arr: A[]) => A[]All return new arrays.
Constructors
Section titled “Constructors”range(start: number, end: number): number[]repeat<A>(n: number): (value: A) => A[]times(n: number): <A>(f: (i: number) => A) => A[]unfold<A, B>(seed: B): (f: (seed: B) => [A, B] | undefined) => A[]Examples
Section titled “Examples”import { pipe } from '@stopcock/fp'import * as A from '@stopcock/fp/array'
type Product = { name: string price: number category: string inStock: boolean}
// the compiler can fuse filter -> map -> take into one loop;// plain pipe just runs each step in orderconst deals = pipe( products, A.filter((p: Product) => p.inStock && p.price < 50), A.map((p) => ({ name: p.name, price: p.price })), A.take(10),)
// group + countconst byCategory = pipe( products, A.groupBy((p: Product) => p.category),)// { electronics: [...], clothing: [...] }
// rolling totalpipe( [100, 200, 150, 300], A.scan((acc, x) => acc + x, 0),)// [100, 300, 450, 750]
// remove duplicates by keyconst uniqueCustomers = pipe( orders, A.uniqBy((o: { customerId: string }) => o.customerId),)
// set operationsconst newUsers = pipe(allUsers, A.difference(existingUsers))const commonTags = pipe(tagsA, A.intersection(tagsB))
// batch processingconst batches = pipe(records, A.chunk(100))