Skip to content

API reference ​

Generated from the TSDoc in src/, with the export inventory read from the built package at runtime — so anything exported but undocumented fails the build rather than quietly going missing.

Why not TypeDoc

TypeDoc is built on the TypeScript compiler API, and TypeScript 7.0 does not ship one — its package exports maps "." to a three-line version.cjs. TypeDoc's peer range tops out at 6.0.x. The same constraint rules out ts-morph, api-extractor and tsd. TypeScript 7.1 is expected to ship a new API; this generator can be retired then.

smullyan/birds ​

ExportKindSummary
apconstReader applicative application.
applyconstExplicit function application.
applyToconstApply a function to a value, value first.
BconstCompose two functions, right to left.
B1constCompose a unary function onto a curried binary one.
B2constCompose a unary function onto a curried ternary one.
B3constCompose three functions right to left.
becardconstThe Becard, by name.
BecardinterfaceThe Becard — B3.
blackbirdconstThe Blackbird, by name.
BlackbirdinterfaceThe Blackbird — B1.
bluebirdconstThe Bluebird, by name.
BluebirdinterfaceThe Bluebird — B.
buntingconstThe Bunting, by name.
BuntinginterfaceThe Bunting — B2.
CconstFlip the argument order of a curried binary function.
cardinalconstThe Cardinal, by name.
CardinalinterfaceThe Cardinal — C.
cardinalOnceRemovedconstThe Cardinal once removed, by name.
CardinalOnceRemovedinterfaceThe Cardinal once removed — C*.
composeconstRight-to-left function composition.
compose2constCompose onto a binary function.
compose3constThree-way right-to-left composition.
constantconstThe constant function.
convergeconstConverge two functions on one value.
CStarconstFlip the last two arguments of a curried ternary function.
DconstApply a binary function with its second argument pre-processed.
D1constApply a ternary function with its third argument pre-processed.
D2constCombine two values, each transformed by its own function.
dickcisselconstThe Dickcissel, by name.
DickcisselinterfaceThe Dickcissel — D1.
doveconstThe Dove, by name.
DoveinterfaceThe Dove — D.
dovekiesconstThe Dovekies, by name.
DovekiesinterfaceThe Dovekies — D2.
duplicateconstApply a curried binary function to the same argument twice.
EconstApply a binary function whose second argument comes from another binary function.
eagleconstThe Eagle, by name.
EagleinterfaceThe Eagle — E.
FconstHold two values and apply a function to them reversed.
finchconstThe Finch, by name.
FinchinterfaceThe Finch — F.
fixconstThe fixed-point combinator.
flipconstFlip a curried binary function's argument order.
GconstCombine an untouched argument with a transformed one, crosswise.
goldfinchconstThe Goldfinch, by name.
GoldfinchinterfaceThe Goldfinch — G.
HconstApply a ternary function with its first argument reused as the third.
hummingbirdconstThe Hummingbird, by name.
HummingbirdinterfaceThe Hummingbird — H.
IconstReturn the argument unchanged.
identityconstThe identity function.
idiotconstThe Idiot, by name.
IdiotinterfaceThe Idiot — I.
idiotOnceRemovedconstThe Idiot once removed, by name.
IdiotOnceRemovedinterfaceThe Idiot once removed — I*.
IStarconstApply a function to a value.
JconstApply a binary accumulating function twice, nested.
jayconstThe Jay, by name.
JayinterfaceThe Jay — J.
KconstProduce a function that ignores its argument and always returns a.
kestrelconstThe Kestrel, by name.
KestrelinterfaceThe Kestrel — K.
KIconstProduce a function that ignores the first argument and returns the second.
kiteconstThe Kite, by name.
KiteinterfaceThe Kite — KI.
LconstSelf-apply the second argument, then pass the result to the first.
larkconstThe Lark, by name.
LarkinterfaceThe Lark — L.
MconstApply a function to itself.
mockingbirdconstThe Mockingbird, by name.
MockingbirdinterfaceThe Mockingbird — M.
OconstApply f to g, then g to the result.
onconstCombine two values on a shared projection.
owlconstThe Owl, by name.
OwlinterfaceThe Owl — O.
PconstThe Psi bird, by symbol.
pairconstChurch-encoded pair.
PhiconstThe Phoenix, by symbol.
phoenixconstApply two functions to one value and combine the results.
PhoenixinterfaceThe Phoenix — Φ.
pipe2constLeft-to-right composition.
psiconstCombine two values after mapping both through the same function.
PsiinterfaceThe Psi bird — Ψ.
QconstCompose two functions left to right.
Q1constApply x to g, then f to the result.
Q2constApply x to f, then g to the result.
Q3constApply f to g, then x to the result.
Q4constApply g to f, then x to the result.
quackyconstThe Quacky bird, by name.
QuackyinterfaceThe Quacky bird — Q4.
queerconstThe Queer bird, by name.
QueerinterfaceThe Queer bird — Q.
quirkyconstThe Quirky bird, by name.
QuirkyinterfaceThe Quirky bird — Q3.
quixoticconstThe Quixotic bird, by name.
QuixoticinterfaceThe Quixotic bird — Q1.
quizzicalconstThe Quizzical bird, by name.
QuizzicalinterfaceThe Quizzical bird — Q2.
RconstApply a binary function to arguments supplied out of order.
robinconstThe Robin, by name.
RobininterfaceThe Robin — R.
SconstApply f and g to the same argument, then apply the results.
sageconstThe Sage bird, by name.
SageinterfaceThe Sage bird — Y.
SageSelfinterfaceThe self-applicable shape used by the Sage bird's fixed-point construction.
SelfApplicableinterfaceRecursive types for the hard forest.
starlingconstThe Starling, by name.
StarlinginterfaceThe Starling — S.
TconstApply a function to a value, value first.
thrushconstThe Thrush, by name.
ThrushinterfaceThe Thrush — T.
turingconstThe Turing bird, by name.
TuringinterfaceThe Turing bird — U.
TuringSelfinterfaceThe self-applicable shape used by the Turing bird, whose self-application is followed by a further argument.
UconstThe Turing bird.
VconstBuild a pair as a function awaiting its consumer.
vireoconstThe Vireo, by name.
VireointerfaceThe Vireo — V.
WconstApply a curried binary function to the same argument twice.
warblerconstThe Warbler, by name.
WarblerinterfaceThe Warbler — W.
warblerOnceRemovedconstThe Warbler once removed, by name.
WarblerOnceRemovedinterfaceThe Warbler once removed — W*.
WStarconstApply a ternary function with its second argument duplicated into the third.
YconstCompute the fixed point of f, giving a recursive function with no name.

ap ​

ts
const ap: Starling

Reader applicative application. Identical to {@link S}.

Source: src/birds/starling.ts

apply ​

ts
const apply: IdiotOnceRemoved

Explicit function application. Identical to {@link IStar}.

Source: src/birds/idiot-once-removed.ts

applyTo ​

ts
const applyTo: Thrush

Apply a function to a value, value first. Identical to {@link T}.

Source: src/birds/thrush.ts

B ​

ts
const B: Bluebird

Compose two functions, right to left.

@example

ts
import { B } from 'smullyan/birds'

const inc = (n: number): number => n + 1
const show = (n: number): string => String(n)

const incThenShow = B(show)(inc)
incThenShow(1) // '2'

Source: src/birds/bluebird.ts

B1 ​

ts
const B1: Blackbird

Compose a unary function onto a curried binary one.

@example

ts
import { B1 } from 'smullyan/birds'

const add = (a: number) => (b: number): number => a + b
const show = (n: number): string => String(n)

const addThenShow = B1(show)(add)
addThenShow(40)(2) // '42'

Source: src/birds/blackbird.ts

B2 ​

ts
const B2: Bunting

Compose a unary function onto a curried ternary one.

@example

ts
import { B2 } from 'smullyan/birds'

const add3 = (a: number) => (b: number) => (c: number): number => a + b + c
const show = (n: number): string => String(n)

B2(show)(add3)(20)(20)(2) // '42'

Source: src/birds/bunting.ts

B3 ​

ts
const B3: Becard

Compose three functions right to left.

@example

ts
import { B3 } from 'smullyan/birds'

const inc = (n: number): number => n + 1
const dbl = (n: number): number => n * 2
const show = (n: number): string => String(n)

B3(show)(dbl)(inc)(20) // '42'

Source: src/birds/becard.ts

becard ​

ts
const becard: Becard

The Becard, by name. Identical to {@link B3}.

Source: src/birds/becard.ts

Becard ​

ts
interface Becard {
  <C, D>(f: (c: C) => D): <B>(g: (b: B) => C) => <A>(h: (a: A) => B) => (a: A) => D;
}

The Becard — B3.

text
B3 f g h x = f (g (h x))

Three-way right-to-left composition. B3 ≡ B (B B) B, though — as with the Blackbird — that derivation is not typeable in TypeScript. The law suite asserts the rank-1 equivalent B3 f g h ≡ B (B f g) h.

Source: src/birds/becard.ts

blackbird ​

ts
const blackbird: Blackbird

The Blackbird, by name. Identical to {@link B1}.

Source: src/birds/blackbird.ts

Blackbird ​

ts
interface Blackbird {
  <C, D>(f: (c: C) => D): <A, B>(g: (a: A) => (b: B) => C) => (a: A) => (b: B) => D;
}

The Blackbird — B1.

text
B1 f g x y = f (g x y)

Composition that reaches over a two-argument function. Where the Bluebird composes onto a unary function, the Blackbird composes onto a binary one.

The classical derivation is B1 ≡ B B B, and it holds at runtime — but it is NOT expressible in TypeScript. Passing B to itself requires instantiating a generic combinator at a polytype, i.e. higher-rank polymorphism, which TypeScript does not have. Explicit type arguments do not rescue it either; the compiler reports that "'B' could be instantiated with an arbitrary type which could be unrelated to 'number'".

That boundary is asserted as a compile-time fact in test/birds.negative.test-d.ts, so if a future TypeScript release gains the expressiveness, the test fails loudly and this note can be revisited. The law suite asserts the rank-1 equivalent instead: B1 f g x ≡ B f (g x).

Useful whenever you want to post-process a binary operation without rewrapping it — B1(not)(equals) is notEquals.

Source: src/birds/blackbird.ts

bluebird ​

ts
const bluebird: Bluebird

The Bluebird, by name. Identical to {@link B}.

Source: src/birds/bluebird.ts

Bluebird ​

ts
interface Bluebird {
  <B, C>(f: (b: B) => C): <A>(g: (a: A) => B) => (a: A) => C;
}

The Bluebird — B.

text
B f g x = f (g x)

Function composition. Smullyan's Bluebird is the combinator that lets one bird's response become another's call, and it is the backbone of the whole forest: B plus C (Cardinal) plus W (Warbler) plus K (Kestrel) is a basis for all of combinatory logic.

Authoring pattern — every combinator in this library follows it:

  1. A named interface declaring the call signature.
  2. An annotated const implementing it.
  3. Alias consts for the bird name and the familiar FP name.

The interface exists because isolatedDeclarations is enabled for src/, which requires every exported binding to carry a syntactically-derivable type. That turns the public type surface into a written artifact rather than an inference result that can drift between compiler releases.

CAVEAT, and it is a real one: nothing checks that the interface AGREES with the implementation beyond assignability. An interface declared WIDER than the implementation compiles and ships. The property-based law suite is the only net, which is why every law must exercise the exported const and never an internal helper.

GENERIC SCOPING — the rule every combinator here must follow:

Each type parameter belongs on the call signature that SUPPLIES it, never on an earlier one. Writing the signature as

<A, B, C>(f: (b: B) => C): (g: (a: A) => B) => (a: A) => C

compiles fine and is wrong: A appears nowhere in f, so at the first call TypeScript has nothing to infer it from and silently defaults it to unknown. Every later application then fails with "Type 'unknown' is not assignable to type 'number'". Scoping A to the second call fixes it.

This is the single most common way a curried combinator's typings go wrong, and positive expectTypeOf assertions alone will not always reveal it — which is why the negative @ts-expect-error suite exists.

Source: src/birds/bluebird.ts

bunting ​

ts
const bunting: Bunting

The Bunting, by name. Identical to {@link B2}.

Source: src/birds/bunting.ts

Bunting ​

ts
interface Bunting {
  <D, E>(
    f: (d: D) => E,
  ): <A, B, C>(g: (a: A) => (b: B) => (c: C) => D) => (a: A) => (b: B) => (c: C) => E;
}

The Bunting — B2.

text
B2 f g x y z = f (g x y z)

The Blackbird's bigger sibling: compose a unary function onto a curried TERNARY one. Where B reaches over one argument and B1 over two, B2 reaches over three.

Source: src/birds/bunting.ts

C ​

ts
const C: Cardinal

Flip the argument order of a curried binary function.

@example

ts
import { C } from 'smullyan/birds'

const concat = (a: string) => (b: string): string => a + b
concat('foo')('bar') // 'foobar'

const flipped = C(concat)
flipped('foo')('bar') // 'barfoo'

Source: src/birds/cardinal.ts

cardinal ​

ts
const cardinal: Cardinal

The Cardinal, by name. Identical to {@link C}.

Source: src/birds/cardinal.ts

Cardinal ​

ts
interface Cardinal {
  <A, B, C>(f: (a: A) => (b: B) => C): (b: B) => (a: A) => C;
}

The Cardinal — C.

text
C f x y = f y x

Argument flipping. The Cardinal takes a curried two-argument function and returns one that expects its arguments in the opposite order.

The Cardinal is its own inverse — C (C f) ≡ f — which is asserted as a property in the law suite. Together with the Bluebird and the Warbler it forms the BCKW basis, one of the classical alternatives to SKI.

Note the generic scoping: A and B are both recoverable from f, so both belong on the first call signature. Only the result type flows through unchanged. Contrast the Bluebird, where A is invisible at the first call and must be deferred to the second.

Source: src/birds/cardinal.ts

cardinalOnceRemoved ​

ts
const cardinalOnceRemoved: CardinalOnceRemoved

The Cardinal once removed, by name. Identical to {@link CStar}.

Source: src/birds/cardinal-once-removed.ts

CardinalOnceRemoved ​

ts
interface CardinalOnceRemoved {
  <A, B, C, D>(f: (a: A) => (c: C) => (b: B) => D): (a: A) => (b: B) => (c: C) => D;
}

The Cardinal once removed — C*.

text
C* f x y z = f x z y

The Cardinal shifted one argument deeper: the first argument passes through and the LAST TWO are exchanged. C* ≡ B C, asserted in the law suite. Like the Cardinal, it is its own inverse.

Source: src/birds/cardinal-once-removed.ts

compose ​

ts
const compose: Bluebird

Right-to-left function composition. Identical to {@link B}.

Source: src/birds/bluebird.ts

compose2 ​

ts
const compose2: Blackbird

Compose onto a binary function. Identical to {@link B1}.

Source: src/birds/blackbird.ts

compose3 ​

ts
const compose3: Becard

Three-way right-to-left composition. Identical to {@link B3}.

Source: src/birds/becard.ts

constant ​

ts
const constant: Kestrel

The constant function. Identical to {@link K}.

Source: src/birds/kestrel.ts

converge ​

ts
const converge: Phoenix

Converge two functions on one value. Identical to {@link phoenix}.

Source: src/birds/phoenix.ts

CStar ​

ts
const CStar: CardinalOnceRemoved

Flip the last two arguments of a curried ternary function.

@example

ts
import { CStar } from 'smullyan/birds'

const triple = (a: string) => (b: string) => (c: string): string => a + b + c
CStar(triple)('x')('y')('z') // 'x' + 'z' + 'y'

Source: src/birds/cardinal-once-removed.ts

D ​

ts
const D: Dove

Apply a binary function with its second argument pre-processed.

@example

ts
import { D } from 'smullyan/birds'

const add = (a: number) => (b: number): number => a + b
const len = (s: string): number => s.length

D(add)(39)(len)('abc') // 42

Source: src/birds/dove.ts

D1 ​

ts
const D1: Dickcissel

Apply a ternary function with its third argument pre-processed.

Source: src/birds/dickcissel.ts

D2 ​

ts
const D2: Dovekies

Combine two values, each transformed by its own function.

@example

ts
import { D2 } from 'smullyan/birds'

const add = (a: number) => (b: number): number => a + b
const len = (s: string): number => s.length
const inc = (n: number): number => n + 1

D2(add)(len)('abc')(inc)(38) // 3 + 39

Source: src/birds/dovekies.ts

dickcissel ​

ts
const dickcissel: Dickcissel

The Dickcissel, by name. Identical to {@link D1}.

Source: src/birds/dickcissel.ts

Dickcissel ​

ts
interface Dickcissel {
  <X, Y, R, S>(
    f: (x: X) => (y: Y) => (r: R) => S,
  ): (x: X) => (y: Y) => <Z>(g: (z: Z) => R) => (z: Z) => S;
}

The Dickcissel — D1.

text
D1 f x y g z = f x y (g z)

The Dove one argument deeper: apply a curried TERNARY function, transforming only its third argument.

Source: src/birds/dickcissel.ts

dove ​

ts
const dove: Dove

The Dove, by name. Identical to {@link D}.

Source: src/birds/dove.ts

Dove ​

ts
interface Dove {
  <X, R, S>(f: (x: X) => (r: R) => S): (x: X) => <Y>(g: (y: Y) => R) => (y: Y) => S;
}

The Dove — D.

text
D f x g y = f x (g y)

Apply a binary function, transforming only the SECOND argument. D ≡ B B, asserted in the law suite.

Source: src/birds/dove.ts

dovekies ​

ts
const dovekies: Dovekies

The Dovekies, by name. Identical to {@link D2}.

Source: src/birds/dovekies.ts

Dovekies ​

ts
interface Dovekies {
  <R, S, T>(
    f: (r: R) => (s: S) => T,
  ): <X>(g: (x: X) => R) => (x: X) => <Y>(h: (y: Y) => S) => (y: Y) => T;
}

The Dovekies — D2.

text
D2 f g x h y = f (g x) (h y)

Transform BOTH arguments of a binary function, each by its own transformer. Compare the Psi bird, which applies the SAME transformer to both — the Dovekies is Psi's asymmetric cousin, and D2 f g g ≡ Ψ f g when the two transformers coincide. The law suite asserts that.

Source: src/birds/dovekies.ts

duplicate ​

ts
const duplicate: Warbler

Apply a curried binary function to the same argument twice. Identical to {@link W}.

Source: src/birds/warbler.ts

E ​

ts
const E: Eagle

Apply a binary function whose second argument comes from another binary function.

Source: src/birds/eagle.ts

eagle ​

ts
const eagle: Eagle

The Eagle, by name. Identical to {@link E}.

Source: src/birds/eagle.ts

Eagle ​

ts
interface Eagle {
  <X, R, S>(
    f: (x: X) => (r: R) => S,
  ): (x: X) => <Y, Z>(g: (y: Y) => (z: Z) => R) => (y: Y) => (z: Z) => S;
}

The Eagle — E.

text
E f x g y z = f x (g y z)

Apply a binary function whose second argument is itself produced by a binary function. E ≡ B (B B B), though as with the other higher compositions that derivation is not typeable here.

Source: src/birds/eagle.ts

F ​

ts
const F: Finch

Hold two values and apply a function to them reversed.

@example

ts
import { F } from 'smullyan/birds'

const cat = (a: string) => (b: string): string => a + b
F('foo')('bar')(cat) // 'barfoo'

Source: src/birds/finch.ts

finch ​

ts
const finch: Finch

The Finch, by name. Identical to {@link F}.

Source: src/birds/finch.ts

Finch ​

ts
interface Finch {
  <A>(a: A): <B>(b: B) => <C>(f: (b: B) => (a: A) => C) => C;
}

The Finch — F.

text
F x y f = f y x

Hold two values, then apply a function to them in reverse order. The Finch is the Vireo's mirror — F ≡ C V — which the law suite asserts.

Source: src/birds/finch.ts

fix ​

ts
const fix: Sage

The fixed-point combinator. Identical to {@link Y}.

Source: src/birds/sage.ts

flip ​

ts
const flip: Cardinal

Flip a curried binary function's argument order. Identical to {@link C}.

Source: src/birds/cardinal.ts

G ​

ts
const G: Goldfinch

Combine an untouched argument with a transformed one, crosswise.

@example

ts
import { G } from 'smullyan/birds'

const sub = (a: number) => (b: number): number => a - b
const len = (s: string): number => s.length

G(sub)(len)('abc')(10) // 10 - 3

Source: src/birds/goldfinch.ts

goldfinch ​

ts
const goldfinch: Goldfinch

The Goldfinch, by name. Identical to {@link G}.

Source: src/birds/goldfinch.ts

Goldfinch ​

ts
interface Goldfinch {
  <Y, R, S>(f: (y: Y) => (r: R) => S): <X>(g: (x: X) => R) => (x: X) => (y: Y) => S;
}

The Goldfinch — G.

text
G f g x y = f y (g x)

Transform one argument, leave the other alone, and combine them crosswise. The Goldfinch is B B C composed with itself in the sense that it both flips and pre-processes — useful for comparators where only one side needs projecting.

Source: src/birds/goldfinch.ts

H ​

ts
const H: Hummingbird

Apply a ternary function with its first argument reused as the third.

@example

ts
import { H } from 'smullyan/birds'

const between = (a: number) => (b: number) => (c: number): string =>
  `${a}-${b}-${c}`

H(between)(1)(2) // '1-2-1'

Source: src/birds/hummingbird.ts

hummingbird ​

ts
const hummingbird: Hummingbird

The Hummingbird, by name. Identical to {@link H}.

Source: src/birds/hummingbird.ts

Hummingbird ​

ts
interface Hummingbird {
  <A, B, C>(f: (a: A) => (b: B) => (a2: A) => C): (a: A) => (b: B) => C;
}

The Hummingbird — H.

text
H f x y = f x y x

Supply the first argument twice, once at each end. The Hummingbird is the Warbler's three-argument relative — where W duplicates into adjacent positions, H duplicates across a gap.

Source: src/birds/hummingbird.ts

I ​

ts
const I: Idiot

Return the argument unchanged.

@example

ts
import { I } from 'smullyan/birds'

I(42)      // 42
I('same')  // 'same'

Source: src/birds/idiot.ts

identity ​

ts
const identity: Idiot

The identity function. Identical to {@link I}.

Source: src/birds/idiot.ts

idiot ​

ts
const idiot: Idiot

The Idiot, by name. Identical to {@link I}.

Source: src/birds/idiot.ts

Idiot ​

ts
interface Idiot {
  <A>(a: A): A;
}

The Idiot — I.

text
I x = x

The identity function, and the simplest bird in the forest. Smullyan calls it the Idiot because it merely echoes back whatever it is called with.

I is derivable rather than primitive: S K K ≡ I and W K ≡ I. Both are asserted in the law suite, which is the cheapest possible check that the Starling, Kestrel and Warbler all agree with each other.

Source: src/birds/idiot.ts

idiotOnceRemoved ​

ts
const idiotOnceRemoved: IdiotOnceRemoved

The Idiot once removed, by name. Identical to {@link IStar}.

Source: src/birds/idiot-once-removed.ts

IdiotOnceRemoved ​

ts
interface IdiotOnceRemoved {
  <A, B>(f: (a: A) => B): (a: A) => B;
}

The Idiot once removed — I*.

text
I* f x = f x

Explicit function application. I* is the Identity bird lifted one level: where I returns its argument, I* returns its argument APPLIED. It is extensionally equal to I on functions — I* f ≡ I f — which the law suite asserts, and it is what most languages call apply.

Source: src/birds/idiot-once-removed.ts

IStar ​

ts
const IStar: IdiotOnceRemoved

Apply a function to a value.

@example

ts
import { IStar } from 'smullyan/birds'

const inc = (n: number): number => n + 1
IStar(inc)(41) // 42

Source: src/birds/idiot-once-removed.ts

J ​

ts
const J: Jay

Apply a binary accumulating function twice, nested.

@example

ts
import { J } from 'smullyan/birds'

const cat = (a: string) => (b: string): string => a + b
J(cat)('a')('b')('c') // 'a' + ('c' + 'b') = 'acb'

Source: src/birds/jay.ts

jay ​

ts
const jay: Jay

The Jay, by name. Identical to {@link J}.

Source: src/birds/jay.ts

Jay ​

ts
interface Jay {
  <A, B>(f: (a: A) => (b: B) => B): (x: A) => (y: B) => (z: A) => B;
}

The Jay — J.

text
J f x y z = f x (f z y)

The Jay uses the same function twice, nesting one application inside the other. Note the type constraint this forces: f's result must be assignable to f's own second parameter, since f z y is fed back in as an argument. That makes f an accumulating operation — exactly the shape of a fold step.

Source: src/birds/jay.ts

K ​

ts
const K: Kestrel

Produce a function that ignores its argument and always returns a.

@example

ts
import { K } from 'smullyan/birds'

const always42 = K(42)
always42('ignored')  // 42
always42(null)       // 42

Source: src/birds/kestrel.ts

kestrel ​

ts
const kestrel: Kestrel

The Kestrel, by name. Identical to {@link K}.

Source: src/birds/kestrel.ts

Kestrel ​

ts
interface Kestrel {
  <A>(a: A): <B>(b: B) => A;
}

The Kestrel — K.

text
K x y = x

The constant function. K takes a value and returns a function that ignores its argument and yields that value. In Smullyan's forest the Kestrel is the bird whose response to any call is always the same.

K is one half of the SKI basis, and S K K ≡ I — applying the Starling to two Kestrels reconstructs the Identity bird. That derivation is asserted in the law suite once the Starling lands.

B is inferred at the SECOND call, so it is scoped there: nothing about the discarded argument is knowable from a alone.

Source: src/birds/kestrel.ts

KI ​

ts
const KI: Kite

Produce a function that ignores the first argument and returns the second.

@example

ts
import { KI } from 'smullyan/birds'

KI('discarded')(42) // 42

Source: src/birds/kite.ts

kite ​

ts
const kite: Kite

The Kite, by name. Identical to {@link KI}.

Source: src/birds/kite.ts

Kite ​

ts
interface Kite {
  <A>(a: A): <B>(b: B) => B;
}

The Kite — KI.

text
KI x y = y

The Kestrel's mirror: ignore the FIRST argument and return the second.

The name is literal — the Kite is K I, the Kestrel applied to the Identity bird. It is also C K, the Cardinal applied to the Kestrel. Both derivations are asserted in the law suite, since a bird that can be built two ways is a bird whose implementation can be checked twice.

Source: src/birds/kite.ts

L ​

ts
const L: Lark

Self-apply the second argument, then pass the result to the first.

@example

ts
import { L } from 'smullyan/birds'
import type { SelfApplicable } from 'smullyan/birds'

const five: SelfApplicable<number> = () => 5
const inc = (n: number): number => n + 1

L(inc)(five) // 6

Source: src/birds/lark.ts

lark ​

ts
const lark: Lark

The Lark, by name. Identical to {@link L}.

Source: src/birds/lark.ts

Lark ​

ts
interface Lark {
  <A, B>(f: (a: A) => B): (y: SelfApplicable<A>) => B;
}

The Lark — L.

text
L x y = x (y y)

Apply y to itself, then hand the result to x. The Lark is the Mockingbird composed with the Bluebird — L ≡ B M — and like the Mockingbird it requires a self-referential type.

L L L is another non-terminating term. As with the Mockingbird, the Lark is useful when the self-applied argument bounds its own recursion.

Source: src/birds/lark.ts

M ​

ts
const M: Mockingbird

Apply a function to itself.

@example

ts
import { M } from 'smullyan/birds'
import type { SelfApplicable } from 'smullyan/birds'

// Safe: ignores its argument, so it terminates.
const answer: SelfApplicable<number> = () => 42
M(answer) // 42

// M(M) would type-check and loop forever — this is the term Ω.

Source: src/birds/mockingbird.ts

mockingbird ​

ts
const mockingbird: Mockingbird

The Mockingbird, by name. Identical to {@link M}.

Source: src/birds/mockingbird.ts

Mockingbird ​

ts
interface Mockingbird {
  <A>(x: SelfApplicable<A>): A;
}

The Mockingbird — M.

text
M x = x x

Self-application, and the bird the book is named for. The Mockingbird responds to every call by repeating it back to itself.

M is NOT typeable in a simply-typed lambda calculus. TypeScript expresses it via {@link SelfApplicable}, a lazily-resolved self-referential interface — see self-application.ts for why that works.

Divergence ​

M(M) is the classic non-terminating term Ω. It type-checks and it hangs. That is not a bug here: Ω has no normal form, so no implementation could do better. Types rule out type errors, not infinite loops.

The Mockingbird is only useful when applied to something that IGNORES or bounds its argument — which is exactly how the Sage bird uses it internally.

Source: src/birds/mockingbird.ts

O ​

ts
const O: Owl

Apply f to g, then g to the result.

@example

ts
import { O } from 'smullyan/birds'

const pick = (_g: (n: number) => string): number => 41
const show = (n: number): string => String(n)

O(pick)(show) // '41'

Source: src/birds/owl.ts

on ​

ts
const on: Psi

Combine two values on a shared projection. Identical to {@link psi}.

Source: src/birds/psi.ts

owl ​

ts
const owl: Owl

The Owl, by name. Identical to {@link O}.

Source: src/birds/owl.ts

Owl ​

ts
interface Owl {
  <A, B>(f: (g: (a: A) => B) => A): (g: (a: A) => B) => B;
}

The Owl — O.

text
O f g = g (f g)

The Owl is grouped with the hard forest by tradition, but it is the odd one out: it is perfectly simply-typeable. No self-application appears in its definition — f is applied to g, and g to the result. The recursion people associate with it comes from what you PASS it, not from the bird.

O ≡ S I, asserted in the law suite. Feeding the Owl a self-applicative argument is what produces fixed-point behaviour, which is why it keeps company with the Mockingbird and the Sage.

Source: src/birds/owl.ts

P ​

ts
const P: Psi

The Psi bird, by symbol. Identical to {@link psi}.

Source: src/birds/psi.ts

pair ​

ts
const pair: Vireo

Church-encoded pair. Identical to {@link V}.

Source: src/birds/vireo.ts

Phi ​

ts
const Phi: Phoenix

The Phoenix, by symbol. Identical to {@link phoenix}.

Source: src/birds/phoenix.ts

phoenix ​

ts
const phoenix: Phoenix

Apply two functions to one value and combine the results.

@example

ts
import { phoenix } from 'smullyan/birds'

const divide = (a: number) => (b: number): number => a / b
const sum = (ns: readonly number[]): number => ns.reduce((a, b) => a + b, 0)
const count = (ns: readonly number[]): number => ns.length

const average = phoenix(divide)(sum)(count)
average([1, 2, 3, 4]) // 2.5

Source: src/birds/phoenix.ts

Phoenix ​

ts
interface Phoenix {
  <B, C, D>(f: (b: B) => (c: C) => D): <A>(g: (a: A) => B) => (h: (a: A) => C) => (a: A) => D;
}

The Phoenix — Φ.

text
Φ f g h x = f (g x) (h x)

Feed one value through two different transformations, then combine the results. Known elsewhere as converge, and as liftA2 for the Reader monad.

The classic use is a fold-free average: Φ(divide)(sum)(length).

A is deferred to the third call because neither f nor g's position in the first signature constrains it — it is first pinned when h arrives, and g must agree with it.

Source: src/birds/phoenix.ts

pipe2 ​

ts
const pipe2: Queer

Left-to-right composition. Identical to {@link Q}.

Source: src/birds/queer.ts

psi ​

ts
const psi: Psi

Combine two values after mapping both through the same function.

@example

ts
import { psi } from 'smullyan/birds'

const compare = (a: number) => (b: number): number => a - b
const len = (s: string): number => s.length

const byLength = psi(compare)(len)
byLength('aaa')('a') // 2

Source: src/birds/psi.ts

Psi ​

ts
interface Psi {
  <B, C>(f: (b1: B) => (b2: B) => C): <A>(g: (a: A) => B) => (a1: A) => (a2: A) => C;
}

The Psi bird — Ψ.

text
Ψ f g x y = f (g x) (g y)

Apply the same transformation to two values, then combine them. This is Haskell's on and Ramda's useWith with a single shared transformer, and it is the bird behind almost every comparator you have ever written: sortBy(Ψ(subtract)(prop('age'))).

Source: src/birds/psi.ts

Q ​

ts
const Q: Queer

Compose two functions left to right.

@example

ts
import { Q } from 'smullyan/birds'

const inc = (n: number): number => n + 1
const show = (n: number): string => String(n)

Q(inc)(show)(41) // '42'

Source: src/birds/queer.ts

Q1 ​

ts
const Q1: Quixotic

Apply x to g, then f to the result.

Source: src/birds/quixotic.ts

Q2 ​

ts
const Q2: Quizzical

Apply x to f, then g to the result.

Source: src/birds/quizzical.ts

Q3 ​

ts
const Q3: Quirky

Apply f to g, then x to the result.

Source: src/birds/quirky.ts

Q4 ​

ts
const Q4: Quacky

Apply g to f, then x to the result.

Source: src/birds/quacky.ts

quacky ​

ts
const quacky: Quacky

The Quacky bird, by name. Identical to {@link Q4}.

Source: src/birds/quacky.ts

Quacky ​

ts
interface Quacky {
  <F>(f: F): <R>(g: (f: F) => R) => <S>(x: (r: R) => S) => S;
}

The Quacky bird — Q4.

text
Q4 f g x = x (g f)

The Quirky bird with its first two arguments exchanged: Q4 ≡ C Q3, asserted in the law suite. It is also the Thrush iterated twice — the value arrives first and each subsequent argument consumes what came before.

Source: src/birds/quacky.ts

queer ​

ts
const queer: Queer

The Queer bird, by name. Identical to {@link Q}.

Source: src/birds/queer.ts

Queer ​

ts
interface Queer {
  <A, B>(f: (a: A) => B): <C>(g: (b: B) => C) => (a: A) => C;
}

The Queer bird — Q.

text
Q f g x = g (f x)

Composition in READING order: Q f g runs f first, then g. The Bluebird composes right-to-left; the Queer bird is its left-to-right twin, and Q ≡ C B — which the law suite asserts.

This is the shape most people mean by "pipe two functions together".

Source: src/birds/queer.ts

quirky ​

ts
const quirky: Quirky

The Quirky bird, by name. Identical to {@link Q3}.

Source: src/birds/quirky.ts

Quirky ​

ts
interface Quirky {
  <G, R>(f: (g: G) => R): (g: G) => <S>(x: (r: R) => S) => S;
}

The Quirky bird — Q3.

text
Q3 f g x = x (f g)

Apply the first argument to the second, then hand the result to the third. The Quirky bird is the Queer bird with its arguments rotated.

Source: src/birds/quirky.ts

quixotic ​

ts
const quixotic: Quixotic

The Quixotic bird, by name. Identical to {@link Q1}.

Source: src/birds/quixotic.ts

Quixotic ​

ts
interface Quixotic {
  <R, S>(f: (r: R) => S): <G>(g: G) => (x: (g: G) => R) => S;
}

The Quixotic bird — Q1.

text
Q1 f g x = f (x g)

One of Smullyan's four Q-birds, which between them cover the permutations of "apply one of three things to another and pass the result on". Here the THIRD argument is the function and the second is its input.

Source: src/birds/quixotic.ts

quizzical ​

ts
const quizzical: Quizzical

The Quizzical bird, by name. Identical to {@link Q2}.

Source: src/birds/quizzical.ts

Quizzical ​

ts
interface Quizzical {
  <F>(f: F): <R, S>(g: (r: R) => S) => (x: (f: F) => R) => S;
}

The Quizzical bird — Q2.

text
Q2 f g x = g (x f)

The Quixotic bird with the roles of the first two arguments exchanged: Q2 ≡ C Q1, asserted in the law suite.

Source: src/birds/quizzical.ts

R ​

ts
const R: Robin

Apply a binary function to arguments supplied out of order.

@example

ts
import { R } from 'smullyan/birds'

const cat = (a: string) => (b: string): string => a + b
R('foo')(cat)('bar') // 'barfoo'

Source: src/birds/robin.ts

robin ​

ts
const robin: Robin

The Robin, by name. Identical to {@link R}.

Source: src/birds/robin.ts

Robin ​

ts
interface Robin {
  <A>(a: A): <B, C>(f: (b: B) => (a: A) => C) => (b: B) => C;
}

The Robin — R.

text
R x f y = f y x

Take a value, then a binary function, then a second value — and apply the function with the arguments in the opposite order to the one they arrived in.

R ≡ B B C, and applying the Robin three times is the identity: R (R (R f)) ≡ f, since the Robin generates a cyclic permutation of three arguments.

Source: src/birds/robin.ts

S ​

ts
const S: Starling

Apply f and g to the same argument, then apply the results.

@example

ts
import { S } from 'smullyan/birds'

const add = (a: number) => (b: number): number => a + b
const inc = (n: number): number => n + 1

// n + (n + 1)
const addToSuccessor = S(add)(inc)
addToSuccessor(20) // 41

Source: src/birds/starling.ts

sage ​

ts
const sage: Sage

The Sage bird, by name. Identical to {@link Y}.

Source: src/birds/sage.ts

Sage ​

ts
interface Sage {
  <A, B>(f: (rec: (a: A) => B) => (a: A) => B): (a: A) => B;
}

The Sage bird — Y.

text
Y f = f (Y f)

The fixed-point combinator, and the reason anonymous functions can recurse. Y takes a function that expects "itself" as its first argument and returns the recursive function, with no name ever bound.

Why this is the Z combinator, not the Y combinator ​

The textbook Y = λf.(λx.f (x x)) (λx.f (x x)) is correct under LAZY evaluation and diverges under eager evaluation: computing f (x x) demands x x before f can decide whether it needs it, and x x expands forever. JavaScript is eager, so the textbook form stack-overflows immediately.

The fix is eta-expansion — wrapping the recursive call in a lambda so it is only forced when an argument actually arrives:

text
Z = λf. (λx. f (λv. x x v)) (λx. f (λv. x x v))

Z is extensionally equal to Y for functions of at least one argument, which is every practical use. The implementation below is Z, written with genuine self-application rather than a named self-reference.

Why not just use a named function? ​

ts
const fix = (f) => { const g = (a) => f(g)(a); return g }  // NOT this

That works, but it defeats the point: g refers to itself by NAME, so it is ordinary recursion wearing a combinator's clothes. The Sage bird's whole purpose is achieving recursion WITHOUT self-reference, using only application. The implementation below does that — rec(rec) is the only mechanism, and rec never mentions itself inside its own body.

Source: src/birds/sage.ts

SageSelf ​

ts
interface SageSelf<A, B> {
  (x: SageSelf<A, B>): (a: A) => B;
}

The self-applicable shape used by the Sage bird's fixed-point construction. Applying it to itself produces the recursive function itself.

Source: src/birds/self-application.ts

SelfApplicable ​

ts
interface SelfApplicable<A> {
  (x: SelfApplicable<A>): A;
}

Recursive types for the hard forest.

Five birds — the Mockingbird, Lark, Owl, Turing bird and Sage — involve SELF-APPLICATION, applying a term to itself. None of them can be typed in a simply-typed lambda calculus: that is a theorem about the calculus, not a shortcoming of TypeScript. In System F you would need something like ∀a. a → a applied to itself, which is ill-founded.

TypeScript gets through because its interface declarations are resolved LAZILY and may reference themselves. SelfApplicable<A> below is a function type whose parameter is its own type — legal here, impossible in Hindley Milner without an explicit iso-recursive wrapper (Haskell's newtype Mu).

This is one of the few places where TypeScript's structural, equirecursive type system is genuinely MORE expressive than Haskell's, rather than less.

The cost is honest and worth stating: a SelfApplicable<A> says nothing about termination. M(M) is well typed and loops forever. The type system is protecting you from type errors, not from divergence — no type system in a Turing-complete language can promise the latter. /

/** A function that can be applied to itself, yielding A.

The self-reference in the parameter position is what makes the Mockingbird expressible.

Source: src/birds/self-application.ts

starling ​

ts
const starling: Starling

The Starling, by name. Identical to {@link S}.

Source: src/birds/starling.ts

Starling ​

ts
interface Starling {
  <A, B, C>(f: (a: A) => (b: B) => C): (g: (a: A) => B) => (a: A) => C;
}

The Starling — S.

text
S f g x = f x (g x)

The substitution combinator, and the workhorse of the SKI basis: together with the Kestrel it can express every other bird in the forest. S K K ≡ I is the classic derivation, asserted in the law suite.

In everyday terms this is ap for the Reader monad — both f and g receive the same environment x, and f's result is applied to g's.

All three type parameters are recoverable from f and g together, but A appears in both, so it must be fixed by the first call for g to check against it. Hence all three sit on the first signature.

Source: src/birds/starling.ts

T ​

ts
const T: Thrush

Apply a function to a value, value first.

@example

ts
import { T } from 'smullyan/birds'

T(41)((n: number) => n + 1) // 42

Source: src/birds/thrush.ts

thrush ​

ts
const thrush: Thrush

The Thrush, by name. Identical to {@link T}.

Source: src/birds/thrush.ts

Thrush ​

ts
interface Thrush {
  <A>(a: A): <B>(f: (a: A) => B) => B;
}

The Thrush — T.

text
T x f = f x

Reversed application: take a value, then take a function, then apply it. The Thrush is C I — the Cardinal applied to the Idiot — which the law suite asserts.

This is the one-argument pipe, and the reason smullyan/pipe exists: a variadic pipe is just the Thrush iterated.

Source: src/birds/thrush.ts

turing ​

ts
const turing: Turing

The Turing bird, by name. Identical to {@link U}.

Source: src/birds/turing.ts

Turing ​

ts
interface Turing {
  <A>(x: TuringSelf<A>): (y: (a: A) => A) => A;
}

The Turing bird — U.

text
U x y = y (x x y)

Named for Alan Turing, who discovered the fixed-point combinator Θ = U U. Applying the Turing bird to itself yields a fixed-point operator: Θ f = f (Θ f).

The x x in the definition is self-application, so this needs the recursive {@link TuringSelf} type.

Why U U is not exported ​

Θ = U U is correct in a lazy language and DIVERGES in an eager one: evaluating f (Θ f) requires evaluating Θ f first, which requires evaluating Θ f again, forever. JavaScript is eager, so U(U) type-checks and immediately overflows the stack.

The usable eager fixed point is the eta-expanded Z combinator — see the Sage bird {@link Y}, which is what you actually want for recursion.

Source: src/birds/turing.ts

TuringSelf ​

ts
interface TuringSelf<A> {
  (x: TuringSelf<A>): (y: (a: A) => A) => A;
}

The self-applicable shape used by the Turing bird, whose self-application is followed by a further argument.

Source: src/birds/self-application.ts

U ​

ts
const U: Turing

The Turing bird. Applied to itself it yields a fixed-point operator — which diverges under eager evaluation. Use {@link Y} instead for real recursion.

@example

ts
import { U } from 'smullyan/birds'
import type { TuringSelf } from 'smullyan/birds'

// A self-applicable that bounds its own recursion.
const stop: TuringSelf<number> = () => () => 0
const inc = (n: number): number => n + 1

U(stop)(inc) // 1

Source: src/birds/turing.ts

V ​

ts
const V: Vireo

Build a pair as a function awaiting its consumer.

@example

ts
import { V, K, KI } from 'smullyan/birds'

const pair = V(1)('two')
pair(K)  // 1
pair(KI) // 'two'

Source: src/birds/vireo.ts

vireo ​

ts
const vireo: Vireo

The Vireo, by name. Identical to {@link V}.

Source: src/birds/vireo.ts

Vireo ​

ts
interface Vireo {
  <A>(a: A): <B>(b: B) => <C>(f: (a: A) => (b: B) => C) => C;
}

The Vireo — V.

text
V x y f = f x y

Hold two values, then hand them to a function. The Vireo is the classical encoding of a PAIR in pure lambda calculus: V a b is the pair, and applying it to K extracts the first element, to KI the second. Both extractions are asserted in the law suite.

Source: src/birds/vireo.ts

W ​

ts
const W: Warbler

Apply a curried binary function to the same argument twice.

@example

ts
import { W } from 'smullyan/birds'

const add = (a: number) => (b: number): number => a + b
const double = W(add)
double(21) // 42

Source: src/birds/warbler.ts

warbler ​

ts
const warbler: Warbler

The Warbler, by name. Identical to {@link W}.

Source: src/birds/warbler.ts

Warbler ​

ts
interface Warbler {
  <A, B>(f: (a: A) => (a2: A) => B): (a: A) => B;
}

The Warbler — W.

text
W f x = f x x

Argument duplication: hand the same value to a curried binary function twice.

W K ≡ I — the Warbler applied to the Kestrel reconstructs the Identity bird, since K x x discards the second copy. Asserted in the law suite.

In monadic terms this is join for the Reader monad: a Reader&lt;R, Reader&lt;R, A>> collapsed by supplying the same environment to both layers.

Source: src/birds/warbler.ts

warblerOnceRemoved ​

ts
const warblerOnceRemoved: WarblerOnceRemoved

The Warbler once removed, by name. Identical to {@link WStar}.

Source: src/birds/warbler-once-removed.ts

WarblerOnceRemoved ​

ts
interface WarblerOnceRemoved {
  <A, B, C>(f: (a: A) => (b: B) => (b2: B) => C): (a: A) => (b: B) => C;
}

The Warbler once removed — W*.

text
W* f x y = f x y y

The Warbler shifted one argument deeper: the FIRST argument passes through untouched and the second is duplicated. W* ≡ B W, asserted in the law suite.

Source: src/birds/warbler-once-removed.ts

WStar ​

ts
const WStar: WarblerOnceRemoved

Apply a ternary function with its second argument duplicated into the third.

Source: src/birds/warbler-once-removed.ts

Y ​

ts
const Y: Sage

Compute the fixed point of f, giving a recursive function with no name.

@example

ts
import { Y } from 'smullyan/birds'

const factorial = Y<number, number>(
  (rec) => (n) => (n <= 1 ? 1 : n * rec(n - 1)),
)

factorial(5) // 120

Source: src/birds/sage.ts

smullyan/agent ​

ExportKindSummary
AttemptsinterfaceHow many times a call may run in total, including the first.
backingOffconstWait according to this schedule between attempts.
BackofftypeA backoff schedule, as data.
callingApiconstLift an async call into a tool, classifying whatever it throws.
cappedAtconstNever wait longer than this, however the schedule grows.
ClockBoundinterfaceThe phrases that need a delay capability.
delayForconstThe delay before attempt n, zero-indexed — delayFor(b, 0) precedes the FIRST retry, not the first call.
deniedconstThe caller lacks permission.
DeniedinterfaceThe caller is not permitted to do this.
DurationinterfaceThe readable dialect.
everyTimeconstWait the same duration before each retry.
explainconstA sentence written for a language model to read.
exponentialconstRetry after a geometrically growing delay.
ExponentialinterfaceWait baseMs * factor^n, optionally capped.
exponentiallyFromconstWait a geometrically growing duration, doubling by default.
failconstA tool call that always fails with e.
fallingBackToconstOn failure, run this instead.
fixedconstRetry after a constant delay.
FixedinterfaceWait the same amount before every retry.
flatMapconstChain another tool call onto a successful result.
fromPromiseconstLift a promise-returning function, classifying anything it throws.
fromThrownconstClassify a thrown value.
ignoringServerAdviceconstFollow the local schedule even when the server sent a Retry-After.
immediateconstRetry with no delay.
ImmediateinterfaceRetry immediately.
immediatelyconstRetry with no delay at all.
inMillisconstThe duration in milliseconds.
invalidArgsconstArguments failed validation.
InvalidArgsinterfaceArguments failed validation before the tool ran.
isRetryableconstWhich failures are worth repeating.
mapconstApply a function to a successful result.
millisconstA duration in milliseconds.
minutesconstA duration in minutes.
notFoundconstThe thing asked for does not exist.
NotFoundinterfaceDotted path to the offending argument, e.g.
onceOnlyconstA budget of exactly one attempt — never retry.
orDefaultingToconstOn failure, succeed with this value instead.
orElseconstFall back to another tool call on failure.
parseconstValidate an unknown value — typically JSON that came back over a wire — as a {@link ToolError}.
rateLimitedconstThe tool was rate limited.
RateLimitedinterfaceA closed, serializable vocabulary of tool failures.
retryconstTotal attempts, INCLUDING the first.
RetryClausetypeOne clause of a retry policy.
RetryPolicyinterfaceHow to retry, as data.
secondsconstA duration in seconds.
SleeptypeA delay capability.
succeedconstA tool call that always succeeds with a.
suggestedDelayMsconstHow long the error itself says to wait, if it says anything.
theValueconstA tool that always succeeds with this value.
timedOutconstThe tool exceeded its time budget.
timeoutconstFail with {@link Timeout} if the call has not settled within ms.
TimeoutinterfaceNear-misses worth trying instead, if the tool can suggest any.
TooltypeA tool call: an asynchronous operation that has not started yet and reports failure in its type.
ToolErrortypeEverything a tool call is allowed to fail with.
unavailableconstThe tool is temporarily unusable.
UnavailableinterfaceThe tool exists but is temporarily unusable — upstream 5xx, cold start, outage.
UnknowninterfaceThe capability that was missing, when it can be named.
unknownErrorconstAn unclassifiable failure.
upToconstAn attempt budget, written as upTo(4).attempts.
whileFailingconstRetry only failures matching this predicate.
whileTransientconstRetry any failure the vocabulary considers transient.
withClockconstRetry according to the given clauses, in any order.
withDefaultconstSupply a value on failure, ending the failure channel.
withinconstAn attempt budget, as a clause.

Attempts ​

ts
interface Attempts {
  readonly _tag: 'Attempts';
  readonly total: number;
}

How many times a call may run in total, including the first.

Source: src/agent/phrases.ts

backingOff ​

ts
const backingOff: (backoff: Backoff) => RetryClause

Wait according to this schedule between attempts.

Source: src/agent/phrases.ts

Backoff ​

ts
type Backoff = Fixed | Exponential | Immediate

A backoff schedule, as data.

Data rather than a closure so a policy can be logged, persisted alongside a durable run, and shown to a model that is deciding whether to keep waiting.

Source: src/agent/tool.ts

callingApi ​

ts
const callingApi: <A>(
  invoke: () => Promise<A>,
  classify?: (e: unknown) => ToolError,
) => Tool<A>

Lift an async call into a tool, classifying whatever it throws.

ts
callingApi(() => gh.issues.get(id), asGithubError)

Source: src/agent/phrases.ts

cappedAt ​

ts
const cappedAt: (d: Duration) => (b: Backoff) => Backoff

Never wait longer than this, however the schedule grows.

Source: src/agent/phrases.ts

ClockBound ​

ts
interface ClockBound {
  /**
   * Retry according to the given clauses, in any order.
   *
   * Accepts `upTo(n).attempts` directly as well as `within(...)`.
   */
  readonly retrying: (
    ...clauses: ReadonlyArray<RetryClause | Attempts>
  ) => <A>(tool: Tool<A>) => Tool<A>;
  /** Fail with a `Timeout` if the call has not answered within this duration. */
  readonly givingUpAfter: (d: Duration) => <A>(tool: Tool<A>) => Tool<A>;
}

The phrases that need a delay capability.

Source: src/agent/phrases.ts

delayFor ​

ts
const delayFor: (backoff: Backoff, attempt: number) => number

The delay before attempt n, zero-indexed — delayFor(b, 0) precedes the FIRST retry, not the first call.

Pure, so a schedule can be asserted without running anything.

Source: src/agent/tool.ts

denied ​

ts
const denied: (reason: string, required?: string) => ToolError

The caller lacks permission.

Source: src/agent/error.ts

Denied ​

ts
interface Denied {
  readonly _tag: 'Denied';
  readonly reason: string;
  /** The capability that was missing, when it can be named. */
  readonly required?: string;
}

The caller is not permitted to do this. Retrying will not help.

Source: src/agent/error.ts

Duration ​

ts
interface Duration {
  readonly _tag: 'Duration';
  readonly ms: number;
}

The readable dialect.

Everything here is a renaming of {@link ./tool} — no new behaviour — chosen so that a call site states its own meaning and a reader (human or model) does not have to fetch a signature to understand it.

The problem this solves ​

timeout(30) is ambiguous: milliseconds or seconds? retry({ times: 4 }) is ambiguous: is the first call one of the four? Those facts live in documentation, so anyone writing code without that documentation loaded guesses — plausibly, and often wrongly.

Wrapping the scalar removes the guess. seconds(30) cannot be misread. upTo(4).attempts settles the off-by-one at the call site. whileFailing (isTransient) says which failures repeat without anyone looking up a default.

Why the clock is bound once ​

Both retrying and timing out need a delay capability, and threading a sleep argument through every phrase would reintroduce exactly the noise this dialect exists to remove. {@link withClock} binds it once at the edge and returns the phrases that need it.

@example

ts
const { retrying, givingUpAfter } = withClock(systemClock)

const fetchIssue = pipe(
  callingApi(() => gh.issues.get(id)),
  retrying(
    whileFailing(isTransient),
    upTo(4).attempts,
    backingOff(exponentiallyFrom(millis(100))),
  ),
  givingUpAfter(seconds(10)),
  fallingBackTo(theValue('degraded')),
)

/

// --- Durations -------------------------------------------------------------

/** A length of time. Constructed by unit, so a call site cannot be misread.

Source: src/agent/phrases.ts

everyTime ​

ts
const everyTime: (d: Duration) => Backoff

Wait the same duration before each retry.

Source: src/agent/phrases.ts

explain ​

ts
const explain: (e: ToolError) => string

A sentence written for a language model to read.

Not a log line. Each message states what failed AND what to do differently, because the consumer is a model deciding its next action. NotFound offers suggestions; InvalidArgs names the path and what was expected; Denied says explicitly not to retry.

Source: src/agent/error.ts

exponential ​

ts
const exponential: (baseMs: number, factor?: number, maxMs?: number) => Backoff

Retry after a geometrically growing delay.

Source: src/agent/tool.ts

Exponential ​

ts
interface Exponential {
  readonly _tag: 'Exponential';
  readonly baseMs: number;
  readonly factor: number;
  readonly maxMs?: number;
}

Wait baseMs * factor^n, optionally capped.

Source: src/agent/tool.ts

exponentiallyFrom ​

ts
const exponentiallyFrom: (d: Duration, doubling?: number) => Backoff

Wait a geometrically growing duration, doubling by default.

Source: src/agent/phrases.ts

fail ​

ts
const fail: <A>(e: ToolError) => Tool<A>

A tool call that always fails with e.

Source: src/agent/tool.ts

fallingBackTo ​

ts
const fallingBackTo: <A>(alternative: () => Tool<A>) => (tool: Tool<A>) => Tool<A>

On failure, run this instead.

Source: src/agent/phrases.ts

fixed ​

ts
const fixed: (ms: number) => Backoff

Retry after a constant delay.

Source: src/agent/tool.ts

Fixed ​

ts
interface Fixed {
  readonly _tag: 'Fixed';
  readonly ms: number;
}

Wait the same amount before every retry.

Source: src/agent/tool.ts

flatMap ​

ts
const flatMap: <A, B>(f: (a: A) => Tool<B>) => (tool: Tool<A>) => Tool<B>

Chain another tool call onto a successful result.

Source: src/agent/tool.ts

fromPromise ​

ts
const fromPromise: <A>(
  f: () => Promise<A>,
  classify?: (e: unknown) => ToolError,
) => Tool<A>

Lift a promise-returning function, classifying anything it throws.

The boundary where an ordinary async API becomes a Tool. Pass classify to map your client's failures onto the vocabulary — an HTTP 429 to rateLimited, a 404 to notFound — so the model receives something it can act on rather than a stringified exception.

Source: src/agent/tool.ts

fromThrown ​

ts
const fromThrown: (e: unknown) => ToolError

Classify a thrown value.

The boundary between "something was thrown" and this vocabulary. Deliberately conservative: anything it cannot confidently classify becomes {@link Unknown} with the original value attached, rather than being guessed into a variant that would change retry behaviour.

Source: src/agent/error.ts

ignoringServerAdvice ​

ts
const ignoringServerAdvice: RetryClause

Follow the local schedule even when the server sent a Retry-After.

Rarely what you want — the server's advice is better information than a local guess — which is why it must be asked for by name.

Source: src/agent/phrases.ts

immediate ​

ts
const immediate: Backoff

Retry with no delay.

Source: src/agent/tool.ts

Immediate ​

ts
interface Immediate {
  readonly _tag: 'Immediate';
}

Retry immediately.

Source: src/agent/tool.ts

immediately ​

ts
const immediately: Backoff

Retry with no delay at all.

Source: src/agent/phrases.ts

inMillis ​

ts
const inMillis: (d: Duration) => number

The duration in milliseconds.

Source: src/agent/phrases.ts

invalidArgs ​

ts
const invalidArgs: (path: string, expected: string, got: unknown) => ToolError

Arguments failed validation.

Source: src/agent/error.ts

InvalidArgs ​

ts
interface InvalidArgs {
  readonly _tag: 'InvalidArgs';
  /** Dotted path to the offending argument, e.g. `"filters.since"`. */
  readonly path: string;
  readonly expected: string;
  readonly got: unknown;
}

Arguments failed validation before the tool ran.

Source: src/agent/error.ts

isRetryable ​

ts
const isRetryable: (e: ToolError) => boolean

Which failures are worth repeating.

A total Record rather than a switch, so adding a variant to {@link ToolError} without deciding its retry behaviour is a COMPILE error — a missing key — not a silent default. / const RETRYABLE: Record<ToolError['_tag'], boolean> = { RateLimited: true, Timeout: true, Unavailable: true, // Unclassified: assume transient once, and let the retry budget bound it. Unknown: true, // Repeating these produces the identical failure and burns context. InvalidArgs: false, Denied: false, NotFound: false, };

/** Is retrying this failure meaningful?

InvalidArgs and Denied are excluded: the same call will fail identically, so retrying burns budget and, in an agent loop, burns context. The model should change the call instead — which is exactly what those variants carry enough information to do.

Source: src/agent/error.ts

map ​

ts
const map: <A, B>(f: (a: A) => B) => (tool: Tool<A>) => Tool<B>

Apply a function to a successful result.

Source: src/agent/tool.ts

millis ​

ts
const millis: (n: number) => Duration

A duration in milliseconds.

Source: src/agent/phrases.ts

minutes ​

ts
const minutes: (n: number) => Duration

A duration in minutes.

Source: src/agent/phrases.ts

notFound ​

ts
const notFound: (searched: string, suggestions?: ReadonlyArray<string>) => ToolError

The thing asked for does not exist.

Source: src/agent/error.ts

NotFound ​

ts
interface NotFound {
  readonly _tag: 'NotFound';
  readonly searched: string;
  /** Near-misses worth trying instead, if the tool can suggest any. */
  readonly suggestions?: ReadonlyArray<string>;
}

Dotted path to the offending argument, e.g. "filters.since". */ readonly path: string; readonly expected: string; readonly got: unknown; }

/** The tool ran and the thing asked for does not exist.

Source: src/agent/error.ts

onceOnly ​

ts
const onceOnly: Attempts

A budget of exactly one attempt — never retry.

Source: src/agent/phrases.ts

orDefaultingTo ​

ts
const orDefaultingTo: <A>(value: A) => (tool: Tool<A>) => Tool<A>

On failure, succeed with this value instead.

Source: src/agent/phrases.ts

orElse ​

ts
const orElse: <A>(alternative: (e: ToolError) => Tool<A>) => (tool: Tool<A>) => Tool<A>

Fall back to another tool call on failure.

Source: src/agent/tool.ts

parse ​

ts
const parse: (u: unknown) => Option<ToolError>

Validate an unknown value — typically JSON that came back over a wire — as a {@link ToolError}.

The whole point of this module is that errors cross boundaries, and anything that crosses a boundary comes back as unknown. Returning Option rather than casting keeps that honest: a malformed envelope is absence, not a ToolError-shaped lie.

Source: src/agent/error.ts

rateLimited ​

ts
const rateLimited: (retryAfterMs: number, limit?: string) => ToolError

The tool was rate limited.

Source: src/agent/error.ts

RateLimited ​

ts
interface RateLimited {
  readonly _tag: 'RateLimited';
  readonly retryAfterMs: number;
  readonly limit?: string;
}

A closed, serializable vocabulary of tool failures.

Why this exists ​

When a tool call fails inside an agent loop, the failure has to travel: back through a tool-result envelope, into the model's context, often across a process or network boundary, and frequently into durable state so a run can be resumed. A thrown Error survives none of that — JSON.stringify(new Error('x')) is {} — and a stringly-typed message gives the model nothing to branch on.

Every variant here is a plain object with a _tag and fields chosen so that BOTH a program and a language model can act on them. RateLimited carries how long to wait. InvalidArgs carries which argument path was wrong and what arrived. NotFound carries what was searched for, so the model can try a different query rather than repeat the same one.

Closed on purpose ​

The union is closed so match is exhaustive and adding a variant is a compile error at every call site. {@link Unknown} is the escape hatch for genuinely unclassifiable failures — it carries the original value as unknown rather than pretending it was an Error. /

/** The tool was rate limited. retryAfterMs is the server's advice, when given.

Source: src/agent/error.ts

retry ​

ts
const retry: (policy: RetryPolicy) => <A>(tool: Tool<A>) => Tool<A>

Total attempts, INCLUDING the first. times: 1 never retries. / readonly times: number; readonly backoff: Backoff; readonly sleep: Sleep; /* Which failures are worth repeating. Required, not defaulted: retrying an InvalidArgs is always wrong, and a silent default would hide that. {@link isRetryable} is the sensible choice for {@link ToolError}. / readonly retryOn: (e: ToolError) => boolean; /** Prefer the delay the error itself advises over the backoff schedule. Only RateLimited carries one. Defaults to true — a server's Retry-After is better information than any local guess. / readonly respectRetryAfter?: boolean; }

/** Retry a tool call according to a policy.

Stops at the first success, the first non-retryable failure, or when the attempt budget is spent — whichever comes first. The failure returned is always the LAST one seen, so the caller sees why it finally gave up rather than why it first stumbled.

@example

ts
const sleep: Sleep = (ms) => new Promise((r) => setTimeout(r, ms))

const resilient = retry({
  times: 4,
  backoff: exponential(200, 2, 5_000),
  sleep,
  retryOn: isRetryable,
})(fetchIssue)

Source: src/agent/tool.ts

RetryClause ​

ts
type RetryClause = WhileFailing | Budget | BackingOff | IgnoringServerAdvice

One clause of a retry policy.

Clauses are order-independent and each names its own role, so a policy reads as a sentence and cannot be assembled by positional accident.

Source: src/agent/phrases.ts

RetryPolicy ​

ts
interface RetryPolicy {
  /** Total attempts, INCLUDING the first. `times: 1` never retries. */
  readonly times: number;
  readonly backoff: Backoff;
  readonly sleep: Sleep;
  /**
   * Which failures are worth repeating. Required, not defaulted: retrying an
   * `InvalidArgs` is always wrong, and a silent default would hide that.
   * {@link isRetryable} is the sensible choice for {@link ToolError}.
   */
  readonly retryOn: (e: ToolError) => boolean;
  /**
   * Prefer the delay the error itself advises over the backoff schedule.
   * Only `RateLimited` carries one. Defaults to true — a server's `Retry-After`
   * is better information than any local guess.
   */
  readonly respectRetryAfter?: boolean;
}

How to retry, as data.

Source: src/agent/tool.ts

seconds ​

ts
const seconds: (n: number) => Duration

A duration in seconds.

Source: src/agent/phrases.ts

Sleep ​

ts
type Sleep = (ms: number) => Promise<void>

A delay capability.

Supply your host's timer once, at the edge:

ts
const sleep: Sleep = (ms) => new Promise((r) => setTimeout(r, ms))

Source: src/agent/tool.ts

succeed ​

ts
const succeed: <A>(a: A) => Tool<A>

A tool call that always succeeds with a.

Source: src/agent/tool.ts

suggestedDelayMs ​

ts
const suggestedDelayMs: (e: ToolError) => Option<number>

How long the error itself says to wait, if it says anything.

Only RateLimited carries server advice. Everything else defers to the caller's backoff policy.

Source: src/agent/error.ts

theValue ​

ts
const theValue: <A>(a: A) => () => Tool<A>

A tool that always succeeds with this value. Reads well inside fallingBackTo.

Source: src/agent/phrases.ts

timedOut ​

ts
const timedOut: (afterMs: number) => ToolError

The tool exceeded its time budget.

Source: src/agent/error.ts

timeout ​

ts
const timeout: (ms: number, sleep: Sleep) => <A>(tool: Tool<A>) => Tool<A>

Fail with {@link Timeout} if the call has not settled within ms.

The underlying work is NOT cancelled — JavaScript has no general mechanism to do so, and pretending otherwise would be a lie. It races the call against the clock and reports which won. If cancellation matters, the tool itself must accept an AbortSignal; this bounds how long you wait, not how long the work runs.

Source: src/agent/tool.ts

Timeout ​

ts
interface Timeout {
  readonly _tag: 'Timeout';
  readonly afterMs: number;
}

Near-misses worth trying instead, if the tool can suggest any. */ readonly suggestions?: ReadonlyArray<string>; }

/** The tool did not answer within its budget.

Source: src/agent/error.ts

Tool ​

ts
type Tool<A> = TaskResult<ToolError, A>

A tool call: an asynchronous operation that has not started yet and reports failure in its type.

ts
type Tool<A> = () => Promise<Result<ToolError, A>>

The thunk is what makes retry and timeout possible at all. A Promise has already started and has cached its outcome, so it cannot be re-run; a Tool is a description of a call, and every combinator here is just a way of describing a different call in terms of it.

The clock is injected ​

setTimeout is a host API, not an ECMAScript one, and this library guarantees its published declarations carry no ambient dependency. So anything that waits takes a {@link Sleep} rather than reaching for a global.

That constraint turned out to be a feature. Backoff schedules are pure data and a fake clock makes them testable exactly, with no wall-clock waiting:

ts
const waits: number[] = []
const fake: Sleep = async (ms) => { waits.push(ms) }
await retry({ times: 3, backoff: exponential(100), sleep: fake, retryOn }) (t)()
// waits === [100, 200]  — asserted, not slept through

Source: src/agent/tool.ts

ToolError ​

ts
type ToolError =
  | RateLimited
  | InvalidArgs
  | NotFound
  | Timeout
  | Unavailable
  | Denied
  | Unknown

Everything a tool call is allowed to fail with.

Source: src/agent/error.ts

unavailable ​

ts
const unavailable: (reason: string, status?: number) => ToolError

The tool is temporarily unusable.

Source: src/agent/error.ts

Unavailable ​

ts
interface Unavailable {
  readonly _tag: 'Unavailable';
  readonly reason: string;
  readonly status?: number;
}

The tool exists but is temporarily unusable — upstream 5xx, cold start, outage.

Source: src/agent/error.ts

Unknown ​

ts
interface Unknown {
  readonly _tag: 'Unknown';
  readonly message: string;
  readonly cause?: unknown;
}

The capability that was missing, when it can be named. */ readonly required?: string; }

/** Unclassifiable failure. Carries the original value without lying about its type.

Source: src/agent/error.ts

unknownError ​

ts
const unknownError: (message: string, cause?: unknown) => ToolError

An unclassifiable failure.

Source: src/agent/error.ts

upTo ​

ts
const upTo: (n: number) => { readonly attempts: Attempts }

An attempt budget, written as upTo(4).attempts.

The trailing .attempts is the point: it makes the unit explicit and settles the perennial off-by-one. Four attempts means one call and three retries.

Source: src/agent/phrases.ts

whileFailing ​

ts
const whileFailing: (predicate: (e: ToolError) => boolean) => RetryClause

Retry only failures matching this predicate. Defaults to {@link isRetryable}.

Source: src/agent/phrases.ts

whileTransient ​

ts
const whileTransient: RetryClause

Retry any failure the vocabulary considers transient.

Source: src/agent/phrases.ts

withClock ​

ts
const withClock: (clock: Sleep) => ClockBound

Retry according to the given clauses, in any order.

Accepts upTo(n).attempts directly as well as within(...). / readonly retrying: ( ...clauses: ReadonlyArray<RetryClause | Attempts> ) => <A>(tool: Tool<A>) => Tool<A>; /** Fail with a Timeout if the call has not answered within this duration. */ readonly givingUpAfter: (d: Duration) => <A>(tool: Tool<A>) => Tool<A>; }

/** Bind a clock once, at the edge, and get the phrases that need it.

ts
const systemClock: Sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const { retrying, givingUpAfter } = withClock(systemClock)

In tests, pass a clock that records instead of waits — backoff schedules then become assertions rather than delays.

Source: src/agent/phrases.ts

withDefault ​

ts
const withDefault: <A>(onError: (e: ToolError) => A) => (tool: Tool<A>) => Tool<A>

Supply a value on failure, ending the failure channel.

Source: src/agent/tool.ts

within ​

ts
const within: (a: Attempts) => RetryClause

An attempt budget, as a clause. upTo(4).attempts is accepted directly.

Source: src/agent/phrases.ts

smullyan/pipe ​

ExportKindSummary
flowconstCompose a sequence of functions into one, left to right.
FlowinterfaceCompose up to twenty functions into one, left to right.
pipeconstThread a value through a sequence of functions, left to right.
Pipeinterfacepipe and flow — left-to-right function application.

flow ​

ts
const flow: Flow

Compose a sequence of functions into one, left to right.

Unlike {@link pipe}, flow takes no value — it returns a function, and the FIRST function may take any number of arguments.

@example

ts
import { flow } from 'smullyan/pipe'

const add = (a: number, b: number): number => a + b
const show = (n: number): string => String(n)

const addThenShow = flow(add, show)
addThenShow(40, 2) // '42'

Source: src/pipe/pipe.ts

Flow ​

ts
interface Flow {
  <A extends ReadonlyArray<unknown>, B>(ab: (...a: A) => B): (...a: A) => B;
  <A extends ReadonlyArray<unknown>, B, C>(ab: (...a: A) => B, bc: (b: B) => C): (...a: A) => C;
  <A extends ReadonlyArray<unknown>, B, C, D>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
  ): (...a: A) => D;
  <A extends ReadonlyArray<unknown>, B, C, D, E>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
  ): (...a: A) => E;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
  ): (...a: A) => F;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
  ): (...a: A) => G;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
  ): (...a: A) => H;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
  ): (...a: A) => I;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
  ): (...a: A) => J;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
  ): (...a: A) => K;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
  ): (...a: A) => L;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
  ): (...a: A) => M;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
  ): (...a: A) => N;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
  ): (...a: A) => O;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
  ): (...a: A) => P;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
  ): (...a: A) => Q;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
  ): (...a: A) => R;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
  ): (...a: A) => S;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
    st: (s: S) => T,
  ): (...a: A) => T;
  <A extends ReadonlyArray<unknown>, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>(
    ab: (...a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
    st: (s: S) => T,
    tu: (t: T) => U,
  ): (...a: A) => U;
}

Compose up to twenty functions into one, left to right.

Source: src/pipe/pipe.ts

pipe ​

ts
const pipe: Pipe

Thread a value through a sequence of functions, left to right.

@example

ts
import { pipe } from 'smullyan/pipe'

const inc = (n: number): number => n + 1
const show = (n: number): string => String(n)

pipe(41, inc, show) // '42'

Source: src/pipe/pipe.ts

Pipe ​

ts
interface Pipe {
  <A>(a: A): A;
  <A, B>(a: A, ab: (a: A) => B): B;
  <A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;
  <A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;
  <A, B, C, D, E>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): E;
  <A, B, C, D, E, F>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
  ): F;
  <A, B, C, D, E, F, G>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
  ): G;
  <A, B, C, D, E, F, G, H>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
  ): H;
  <A, B, C, D, E, F, G, H, I>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
  ): I;
  <A, B, C, D, E, F, G, H, I, J>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
  ): J;
  <A, B, C, D, E, F, G, H, I, J, K>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
  ): K;
  <A, B, C, D, E, F, G, H, I, J, K, L>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
  ): L;
  <A, B, C, D, E, F, G, H, I, J, K, L, M>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
  ): M;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
  ): N;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
  ): O;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
  ): P;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
  ): Q;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
  ): R;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
  ): S;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
    st: (s: S) => T,
  ): T;
  <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>(
    a: A,
    ab: (a: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
    fg: (f: F) => G,
    gh: (g: G) => H,
    hi: (h: H) => I,
    ij: (i: I) => J,
    jk: (j: J) => K,
    kl: (k: K) => L,
    lm: (l: L) => M,
    mn: (m: M) => N,
    no: (n: N) => O,
    op: (o: O) => P,
    pq: (p: P) => Q,
    qr: (q: Q) => R,
    rs: (r: R) => S,
    st: (s: S) => T,
    tu: (t: T) => U,
  ): U;
}

pipe and flow — left-to-right function application.

These are the Thrush ({@link T}) and the Queer bird ({@link Q}) iterated: pipe threads a VALUE through a sequence of functions, flow composes the functions into a new one without supplying a value yet.

text
pipe(x, f, g, h)  ≡  h(g(f(x)))
flow(f, g, h)(x)  ≡  h(g(f(x)))

Why these are overload chains rather than variadic generics ​

TypeScript's variadic tuple types can express "a chain of functions where each output feeds the next", but inference through such a type degrades badly: intermediate positions widen to unknown and the error messages become unreadable. A hand-written overload chain gives exact inference at every arity, at the cost of a large but entirely mechanical file.

This is the ONE place in smullyan where overloads are correct. Combinators are curried and single-signature precisely to avoid overload resolution; pipe and flow are variadic by nature, so there is no single signature to write. See bluebird.ts for the reasoning that applies everywhere else.

Both support up to twenty functions. Beyond that, nest a second pipe. /

/** Thread a value through up to twenty functions, left to right.

Source: src/pipe/pipe.ts

smullyan/option ​

ExportKindSummary
apconstApply a wrapped function to a wrapped value.
filterconstDiscard the value unless it satisfies a predicate.
flatMapconstChain a computation that itself may be absent.
flattenconstRemove one level of nesting.
fromNullableconstConvert a nullable value.
fromPredicateconstKeep a value only if it satisfies a predicate.
fromThrowableconstRun a function that may throw, capturing failure as {@link none}.
getOrElseconstExtract the value, computing a fallback if absent.
isNoneconstType guard narrowing to {@link None}.
isSomeconstType guard narrowing to {@link Some}.
mapconstApply a function to the value if present.
matchconstExhaustively handle both cases.
noneconstThe absent value.
NoneinterfaceThe absent case.
OptiontypeA value that may be absent.
orElseconstFall back to another Option if absent.
sequenceconstTurn a list of Options into an Option of a list.
someconstWrap a present value.
SomeinterfaceOption<A> — a value that may be absent.
toNullableconstCollapse to null when absent.
toUndefinedconstCollapse to undefined when absent.
traverseconstMap each element to an Option, then {@link sequence}.

ap ​

ts
const ap: <A, B>(ff: Option<(a: A) => B>) => (fa: Option<A>) => Option<B>

Apply a wrapped function to a wrapped value. Applicative ap.

Source: src/option/option.ts

filter ​

ts
const filter: <A>(predicate: (a: A) => boolean) => (fa: Option<A>) => Option<A>

Discard the value unless it satisfies a predicate.

Source: src/option/option.ts

flatMap ​

ts
const flatMap: <A, B>(f: (a: A) => Option<B>) => (fa: Option<A>) => Option<B>

Chain a computation that itself may be absent. Monadic bind.

Source: src/option/option.ts

flatten ​

ts
const flatten: <A>(fa: Option<Option<A>>) => Option<A>

Remove one level of nesting.

Source: src/option/option.ts

fromNullable ​

ts
const fromNullable: <A>(a: A | null | undefined) => Option<NonNullable<A>>

Convert a nullable value. Both null and undefined become {@link none}.

@example

ts
fromNullable(document.getElementById('x')) // Option<HTMLElement>

Source: src/option/option.ts

fromPredicate ​

ts
const fromPredicate: <A>(predicate: (a: A) => boolean) => (a: A) => Option<A>

Keep a value only if it satisfies a predicate.

Source: src/option/option.ts

fromThrowable ​

ts
const fromThrowable: <A>(f: () => A) => Option<A>

Run a function that may throw, capturing failure as {@link none}.

The error itself is discarded — use Result.fromThrowable when you need it.

Source: src/option/option.ts

getOrElse ​

ts
const getOrElse: <A>(onNone: () => A) => (fa: Option<A>) => A

Extract the value, computing a fallback if absent.

The fallback is a THUNK so it is not evaluated when the value is present — which matters when producing it is expensive or throws.

Source: src/option/option.ts

isNone ​

ts
const isNone: <A>(fa: Option<A>) => fa is None

Type guard narrowing to {@link None}.

Source: src/option/option.ts

isSome ​

ts
const isSome: <A>(fa: Option<A>) => fa is Some<A>

Type guard narrowing to {@link Some}.

Source: src/option/option.ts

map ​

ts
const map: <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>

Apply a function to the value if present. Functor map.

Source: src/option/option.ts

match ​

ts
const match: <A, B>(onNone: () => B, onSome: (a: A) => B) => (fa: Option<A>) => B

Exhaustively handle both cases.

Takes the absent branch first, matching the declaration order of Option<A> = Some<A> | None read as "failure then success" — the same order Result.match uses, so the two are visually consistent.

Source: src/option/option.ts

none ​

ts
const none: Option<never>

The absent value.

A single frozen constant rather than a function: None carries no payload, so there is nothing to allocate per use.

Source: src/option/option.ts

None ​

ts
interface None {
  readonly _tag: 'None';
}

The absent case. Carries no payload.

Source: src/option/option.ts

Option ​

ts
type Option<A> = Some<A> | None

A value that may be absent.

Source: src/option/option.ts

orElse ​

ts
const orElse: <A>(onNone: () => Option<A>) => (fa: Option<A>) => Option<A>

Fall back to another Option if absent.

Source: src/option/option.ts

sequence ​

ts
const sequence: <A>(fas: ReadonlyArray<Option<A>>) => Option<ReadonlyArray<A>>

Turn a list of Options into an Option of a list.

Absent if ANY element is absent — the standard applicative sequence, and it short-circuits on the first None.

Source: src/option/option.ts

some ​

ts
const some: <A>(value: A) => Option<A>

Wrap a present value.

Source: src/option/option.ts

Some ​

ts
interface Some<A> {
  readonly _tag: 'Some';
  readonly value: A;
}

Option<A> — a value that may be absent.

A discriminated union, not a class. That means it is structurally typed, serialises to plain JSON, and narrows correctly with a bare switch on _tag even if you never touch a helper in this module.

ts
const o: Option<number> = some(42)
if (o._tag === 'Some') o.value // narrowed to number

Why readonly and why a _tag ​

The tag is a string literal rather than a symbol or a class so the value survives JSON.stringify and a structured clone. Everything is readonly because these are values, not containers to mutate.

Data-last, curried ​

Every combinator takes its function first and the Option last, so it drops straight into pipe:

ts
pipe(some(20), map(inc), filter(isEven), getOrElse(() => 0))

Note the annotation style: these are consts with explicit function-type annotations rather than the named-interface pattern the birds use. isolatedDeclarations is satisfied either way; the birds carry named interfaces because those types are part of their documented identity (Bluebird, Cardinal), whereas these are ordinary functions. /

/** The present case.

Source: src/option/option.ts

toNullable ​

ts
const toNullable: <A>(fa: Option<A>) => A | null

Collapse to null when absent.

Source: src/option/option.ts

toUndefined ​

ts
const toUndefined: <A>(fa: Option<A>) => A | undefined

Collapse to undefined when absent.

Source: src/option/option.ts

traverse ​

ts
const traverse: <A, B>(
  f: (a: A) => Option<B>,
) => (as: ReadonlyArray<A>) => Option<ReadonlyArray<B>>

Map each element to an Option, then {@link sequence}.

Source: src/option/option.ts

smullyan/result ​

ExportKindSummary
apconstApply a wrapped function to a wrapped value.
errconstWrap a failure.
ErrinterfaceThe failure case.
flatMapconstChain a computation that may itself fail.
flattenconstRemove one level of nesting.
fromNullableconstConvert a nullable value, using onNullish for the failure case.
fromThrowableconstRun a function that may throw, capturing the thrown value.
getOrElseconstExtract the success value, computing a fallback from the error.
isErrconstType guard narrowing to {@link Err}.
isOkconstType guard narrowing to {@link Ok}.
mapconstApply a function to the success value.
mapErrconstApply a function to the failure value.
matchconstExhaustively handle both cases.
okconstWrap a success.
OkinterfaceResult<E, A> — a computation that either succeeded with A or failed with E.
orElseconstFall back to another Result on failure.
ResulttypeA computation that either succeeded with A or failed with E.
sequenceconstTurn a list of Results into a Result of a list.
traverseconstMap each element to a Result, then {@link sequence}.

ap ​

ts
const ap: <F, A, B>(
  ff: Result<F, (a: A) => B>,
) => <E>(fa: Result<E, A>) => Result<E | F, B>

Apply a wrapped function to a wrapped value. Fails on the FIRST error.

Source: src/result/result.ts

err ​

ts
const err: <E>(error: E) => Result<E, never>

Wrap a failure.

Source: src/result/result.ts

Err ​

ts
interface Err<E> {
  readonly _tag: 'Err';
  readonly error: E;
}

The failure case.

Source: src/result/result.ts

flatMap ​

ts
const flatMap: <A, F, B>(
  f: (a: A) => Result<F, B>,
) => <E>(fa: Result<E, A>) => Result<E | F, B>

Chain a computation that may itself fail.

The error types union rather than being forced to match, so a pipeline can accumulate distinct failure modes without a common base type.

Source: src/result/result.ts

flatten ​

ts
const flatten: <E, F, A>(fa: Result<E, Result<F, A>>) => Result<E | F, A>

Remove one level of nesting.

Source: src/result/result.ts

fromNullable ​

ts
const fromNullable: <E, A>(
  onNullish: () => E,
) => (a: A | null | undefined) => Result<E, NonNullable<A>>

Convert a nullable value, using onNullish for the failure case.

Source: src/result/result.ts

fromThrowable ​

ts
const fromThrowable: <E, A>(f: () => A, onThrow: (e: unknown) => E) => Result<E, A>

Run a function that may throw, capturing the thrown value.

JavaScript lets you throw anything, so what arrives in a catch is genuinely unknown — not Error. Rather than lie about that with a cast, this takes an onThrow mapper and makes you decide. That is the honest signature, and it is why there is no zero-argument overload.

@example

ts
const parsed = fromThrowable(
  () => JSON.parse(input) as unknown,
  (e) => (e instanceof Error ? e.message : 'unknown parse failure'),
)

Source: src/result/result.ts

getOrElse ​

ts
const getOrElse: <E, A>(onErr: (e: E) => A) => (fa: Result<E, A>) => A

Extract the success value, computing a fallback from the error.

Source: src/result/result.ts

isErr ​

ts
const isErr: <E, A>(fa: Result<E, A>) => fa is Err<E>

Type guard narrowing to {@link Err}.

Source: src/result/result.ts

isOk ​

ts
const isOk: <E, A>(fa: Result<E, A>) => fa is Ok<A>

Type guard narrowing to {@link Ok}.

Source: src/result/result.ts

map ​

ts
const map: <A, B>(f: (a: A) => B) => <E>(fa: Result<E, A>) => Result<E, B>

Apply a function to the success value. Failures pass through untouched.

Source: src/result/result.ts

mapErr ​

ts
const mapErr: <E, F>(f: (e: E) => F) => <A>(fa: Result<E, A>) => Result<F, A>

Apply a function to the failure value. Successes pass through untouched.

Source: src/result/result.ts

match ​

ts
const match: <E, A, B>(onErr: (e: E) => B, onOk: (a: A) => B) => (fa: Result<E, A>) => B

Exhaustively handle both cases. Failure branch first, as in Option.match.

Source: src/result/result.ts

ok ​

ts
const ok: <A>(value: A) => Result<never, A>

Wrap a success.

Source: src/result/result.ts

Ok ​

ts
interface Ok<A> {
  readonly _tag: 'Ok';
  readonly value: A;
}

Result<E, A> — a computation that either succeeded with A or failed with E.

Why the error type comes first ​

Result<E, A>, not Result<A, E>. Type parameters in TypeScript are applied left to right, so putting E first is what makes Result<ParseError, _> usable as a partially-applied type in an alias:

ts
type Parsed<A> = Result<ParseError, A>

The cost is that the common case reads "backwards" relative to Promise<A>. That is the standard trade in fp-ts's Either<E, A> and Rust's Result<T, E> chose the opposite; this library follows the former because partial application is the more useful property in a type system without higher-kinded types.

Why Err carries a bare E ​

Err<E> holds whatever you put in it — a string, an enum, an Error, a tagged union of your own. It does NOT impose an error base class or a cause chain.

The reasoning: the moment this module defines an error class, every consumer inherits its opinion about error identity, serialisation and stack capture. Domain errors are usually best modelled as plain discriminated unions, which a bare E supports directly. If you want Error semantics, use Result<Error, A> and nothing is lost.

The one place this shows is {@link fromThrowable}, where JavaScript hands us an unknown — see the note there.

Generic scoping ​

Every combinator here scopes E to the call that SUPPLIES it, exactly as the birds do (see birds/bluebird.ts). Writing map as <E, A, B>(f: (a: A) => B) => (fa: Result<E, A>) => Result<E, B> compiles and is WRONG: E appears nowhere in f, so it defaults to unknown and the error type is silently discarded.

That failure is invisible without a type test, because Result<E, A> is COVARIANT in E — Result<MyError, A> is assignable to Result<unknown, A>, so every call still compiles and every runtime test still passes. Reader<R, A> is contravariant in R and therefore fails loudly under the same mistake. Same bug, opposite volume. /

/** The success case.

Source: src/result/result.ts

orElse ​

ts
const orElse: <E, F, A>(
  onErr: (e: E) => Result<F, A>,
) => (fa: Result<E, A>) => Result<F, A>

Fall back to another Result on failure.

Source: src/result/result.ts

Result ​

ts
type Result<E, A> = Ok<A> | Err<E>

A computation that either succeeded with A or failed with E.

Source: src/result/result.ts

sequence ​

ts
const sequence: <E, A>(fas: ReadonlyArray<Result<E, A>>) => Result<E, ReadonlyArray<A>>

Turn a list of Results into a Result of a list.

Fails with the FIRST error and short-circuits. If you need every error, map to a validation type that accumulates — this is the monadic sequence, and short-circuiting is what makes it monadic.

Source: src/result/result.ts

traverse ​

ts
const traverse: <E, A, B>(
  f: (a: A) => Result<E, B>,
) => (as: ReadonlyArray<A>) => Result<E, ReadonlyArray<B>>

Map each element to a Result, then {@link sequence}.

Source: src/result/result.ts

smullyan/task ​

ExportKindSummary
allconstRun tasks CONCURRENTLY, collecting every result.
apconstRun two tasks CONCURRENTLY and apply the first's function to the second's value.
flatMapconstChain an asynchronous computation.
fromPromiseconstWrap an existing promise.
fromSyncconstLift a synchronous function into a Task.
mapconstApply a function to the eventual value.
ofconstLift a plain value into a Task.
sequentialconstRun tasks in SEQUENCE, each starting only after the previous settles.
TasktypeTask<A> — an asynchronous computation that has not started yet.
TaskResulttypeAn asynchronous computation that may fail with E.
tryCatchconstRun a task, capturing rejection as {@link Err}.

all ​

ts
const all: <A>(tasks: ReadonlyArray<Task<A>>) => Task<ReadonlyArray<A>>

Run tasks CONCURRENTLY, collecting every result.

Source: src/task/task.ts

ap ​

ts
const ap: <A, B>(ff: Task<(a: A) => B>) => (fa: Task<A>) => Task<B>

Run two tasks CONCURRENTLY and apply the first's function to the second's value.

Contrast {@link flatMap}, which is necessarily sequential because the second task depends on the first's result. ap has no such dependency, so running in sequence would waste the parallelism for nothing.

Source: src/task/task.ts

flatMap ​

ts
const flatMap: <A, B>(f: (a: A) => Task<B>) => (fa: Task<A>) => Task<B>

Chain an asynchronous computation.

Source: src/task/task.ts

fromPromise ​

ts
const fromPromise: <A>(f: () => Promise<A>) => Task<A>

Wrap an existing promise.

Note the argument is a THUNK, not a promise. Taking a promise directly would defeat the purpose: it would already be running.

Source: src/task/task.ts

fromSync ​

ts
const fromSync: <A>(f: () => A) => Task<A>

Lift a synchronous function into a Task.

Source: src/task/task.ts

map ​

ts
const map: <A, B>(f: (a: A) => B) => (fa: Task<A>) => Task<B>

Apply a function to the eventual value.

Source: src/task/task.ts

of ​

ts
const of: <A>(a: A) => Task<A>

Lift a plain value into a Task.

Source: src/task/task.ts

sequential ​

ts
const sequential: <A>(tasks: ReadonlyArray<Task<A>>) => Task<ReadonlyArray<A>>

Run tasks in SEQUENCE, each starting only after the previous settles.

Source: src/task/task.ts

Task ​

ts
type Task<A> = () => Promise<A>

Task<A> — an asynchronous computation that has not started yet.

ts
type Task<A> = () => Promise<A>

Why a thunk, and not just a Promise ​

A Promise is EAGER: it begins executing the moment it is constructed, and it caches its result. That makes promises values-in-flight rather than descriptions of work, with three consequences a Task avoids:

  • You cannot retry a Promise. It has already run; awaiting again returns the same settled value. A Task can be invoked as many times as you like.
  • You cannot delay a Promise. Constructing it starts it. Building a pipeline out of promises therefore starts every step immediately.
  • Composition is not referentially transparent. Substituting a promise for the expression that produced it changes when the work happens.

Wrapping in a thunk restores all three. Task<A> is a description of asynchronous work; nothing happens until you call it.

Task never fails ​

A Task<A> models an async computation that SUCCEEDS with A. If it can fail, say so in the type: use {@link TaskResult}, which is Task<Result<E, A>>. That keeps the failure channel explicit and typed rather than hidden in a rejected promise that no signature mentions.

Source: src/task/task.ts

TaskResult ​

ts
type TaskResult<E, A> = Task<Result<E, A>>

An asynchronous computation that may fail with E.

Source: src/task/task.ts

tryCatch ​

ts
const tryCatch: <E, A>(fa: Task<A>, onReject: (e: unknown) => E) => TaskResult<E, A>

Run a task, capturing rejection as {@link Err}.

This is the boundary between the promise world, where failure is untyped and implicit, and the Result world, where it is neither. As with Result.fromThrowable, you supply the mapper because a rejection value is genuinely unknown.

Source: src/task/task.ts

smullyan/reader ​

ExportKindSummary
apconstApply a wrapped function to a wrapped value.
askconstRetrieve the environment itself.
asksconstRetrieve a projection of the environment.
flatMapconstChain a computation that also needs the environment.
flattenconstRemove one level of nesting.
localconstRun a reader in a DERIVED environment.
mapconstApply a function to the result.
ofconstA reader that ignores the environment and returns a constant.
ReadertypeReader<R, A> — a computation awaiting an environment R.
runconstSupply the environment and get the result.

ap ​

ts
const ap: <R, A, B>(ff: Reader<R, (a: A) => B>) => (fa: Reader<R, A>) => Reader<R, B>

Apply a wrapped function to a wrapped value.

This is the Starling: ap ≡ S.

Source: src/reader/reader.ts

ask ​

ts
const ask: <R>() => Reader<R, R>

Retrieve the environment itself.

Extensionally the identity function; named for intent.

Source: src/reader/reader.ts

asks ​

ts
const asks: <R, A>(f: (r: R) => A) => Reader<R, A>

Retrieve a projection of the environment.

Source: src/reader/reader.ts

flatMap ​

ts
const flatMap: <R, A, B>(f: (a: A) => Reader<R, B>) => (fa: Reader<R, A>) => Reader<R, B>

Chain a computation that also needs the environment.

Both the outer and inner reader receive the SAME r — which is exactly what makes this dependency injection rather than mere composition.

Source: src/reader/reader.ts

flatten ​

ts
const flatten: <R, A>(ffa: Reader<R, Reader<R, A>>) => Reader<R, A>

Remove one level of nesting.

This is the Warbler: flatten ≡ W.

Source: src/reader/reader.ts

local ​

ts
const local: <R, S>(f: (s: S) => R) => <A>(fa: Reader<R, A>) => Reader<S, A>

Run a reader in a DERIVED environment.

The contravariant direction: local maps the environment BACKWARDS, letting a component that needs a small environment run inside a larger one.

@example

ts
type App = { readonly db: Db; readonly log: Logger }
const withDb: Reader<Db, string> = (db) => db.name

const inApp: Reader<App, string> = local((app: App) => app.db)(withDb)

Source: src/reader/reader.ts

map ​

ts
const map: <A, B>(f: (a: A) => B) => <R>(fa: Reader<R, A>) => Reader<R, B>

Apply a function to the result.

This is the Bluebird: map ≡ B.

Source: src/reader/reader.ts

of ​

ts
const of: <R, A>(a: A) => Reader<R, A>

A reader that ignores the environment and returns a constant.

This is the Kestrel: of ≡ K.

Source: src/reader/reader.ts

Reader ​

ts
type Reader<R, A> = (r: R) => A

Reader<R, A> — a computation awaiting an environment R.

ts
type Reader<R, A> = (r: R) => A

That is all it is: a function. The value of naming it is that a plain function type has a map, a flatMap and an ap, and once you can see that, dependency injection stops needing a framework.

The birds were secretly about this all along ​

The Reader monad's operations ARE combinators from the aviary, applied to functions rather than to values:

ReaderBirdDefinition
mapBluebird BB f g x = f (g x)
apStarling SS f g x = f x (g x)
flatMapStarling flipped—
joinWarbler WW f x = f x x
ofKestrel KK x y = x

The law suite asserts each of these equivalences directly, which is a pleasingly strong check: the ADT and the combinators have to agree, and they were written independently.

Why ask and not just the identity function ​

ask IS the identity function. Naming it documents intent at the call site — "retrieve the environment" — rather than leaving a bare id for a reader to decode.

Source: src/reader/reader.ts

run ​

ts
const run: <R>(r: R) => <A>(fa: Reader<R, A>) => A

Supply the environment and get the result.

Source: src/reader/reader.ts

MIT licensed.