Function utilities
The Function module provides the small, pure helpers that make Effect’s APIs
ergonomic. It backs the data-first/data-last call style used across the whole
library, the pipeline operators (pipe, flow, compose), and a handful of
identity, constant, and escape-hatch utilities.
Most of these are imported either as named exports from effect (the most
common ones: pipe, flow, identity, absurd, hole) or via the namespace:
import { Function, pipe, flow, identity } from "effect"The common case: pipe and flow
Section titled “The common case: pipe and flow”pipe and flow are two halves of the same idea — feeding a value through a
left-to-right sequence of unary functions.
pipestarts with a value and applies functions to it immediately, returning the final result.flowcomposes the functions into a new function that you can call later.
import { pipe, flow } from "effect"
// `pipe`: apply a value through functions, get the result nowconst result = pipe( 5, (n) => n + 1, // 6 (n) => n * 2, // 12 (n) => `value: ${n}`)// => "value: 12"
// `flow`: compose the same steps into a reusable functionconst transform = flow( (n: number) => n + 1, (n) => n * 2, (n) => `value: ${n}`)
transform(5) // => "value: 12"transform(9) // => "value: 20"In short: pipe(x, f, g) is g(f(x)), while flow(f, g) is (x) => g(f(x)).
Each step after the first must be unary (accept a single argument), because
each function is called with only the previous step’s result. flow is the only
one whose first function may take multiple arguments.
pipe is also a method
Section titled “pipe is also a method”Every Effect data type (and most pipeable values) carries a .pipe method, so
you rarely import pipe directly when working with a single value. These two
are equivalent:
import { Effect, pipe } from "effect"
const a = pipe( Effect.succeed(1), Effect.map((n) => n + 1))
const b = Effect.succeed(1).pipe( Effect.map((n) => n + 1))Use the free pipe function when the starting value is a plain JS value (like a
number or array) that has no .pipe method.
Why data-last? dual in one paragraph
Section titled “Why data-last? dual in one paragraph”The functions you pass to pipe/flow are data-last: the value being
operated on is supplied last (e.g. Array.map(double) returns a function still
waiting for the array). Effect’s combinators are built with dual so
they work both ways — Array.map(xs, double) (data-first) and
pipe(xs, Array.map(double)) (data-last). If you are building your own
dual API, see the dedicated guide at /code-style/dual-apis/;
the reference for dual itself is below.
Reference
Section titled “Reference”Applies an initial value through a left-to-right sequence of unary functions and returns the final result. Equivalent to nested application.
import { pipe } from "effect"
pipe(5, (n) => n + 1, (n) => n * 2)// => 12Composes a left-to-right sequence of functions into a single reusable function. The first function may have any arity; every following function must be unary.
import { flow } from "effect"
const lenThenDouble = flow( (s: string) => s.length, (n) => n * 2)
lenThenDouble("aaa")// => 6compose
Section titled “compose”Composes exactly two unary functions into one. compose(ab, bc) produces
(a) => bc(ab(a)). It is dual, so it also reads data-last in a pipe.
import { Function } from "effect"
const increment = (n: number) => n + 1const square = (n: number) => n * n
Function.compose(increment, square)(2)// => 9 (square(increment(2)) = square(3))Applies a fixed value to a unary function: apply(a)(f) is f(a). Handy when
the function is the value flowing through a pipe.
import { Function, pipe, String } from "effect"
pipe(String.length, Function.apply("hello"))// => 5Builds a function callable in both data-first and data-last (pipe-friendly)
style from a single implementation. Pass either the arity of the
data-first form (the common case) or a predicate over the arguments when
optional parameters make arity ambiguous.
import { Function, pipe } from "effect"
// data-first arity is 2: (self, that)const sum = Function.dual< (that: number) => (self: number) => number, // data-last signature (self: number, that: number) => number // data-first signature>(2, (self, that) => self + that)
sum(2, 3) // => 5 (data-first)pipe(2, sum(3)) // => 5 (data-last)Use a predicate instead of an arity when optional arguments mean the call count alone cannot distinguish the two forms:
import { Function, pipe } from "effect"
const sum = Function.dual< (that: number) => (self: number) => number, (self: number, that: number) => number>( (args) => args.length === 2, // data-first when both args are present (self, that) => self + that)
sum(2, 3) // => 5pipe(2, sum(3)) // => 5See /code-style/dual-apis/ for the full guide on authoring dual APIs.
Reverses the two argument groups of a curried function: f(a)(b) becomes
flip(f)(b)(a).
import { Function } from "effect"
const f = (a: number) => (b: string) => a - b.length
Function.flip(f)("aaa")(2)// => -1 (original: f(2)("aaa") = 2 - 3)tupled
Section titled “tupled”Adapts a multi-argument function so it accepts a single tuple argument instead.
import { Function } from "effect"
const sumTupled = Function.tupled((x: number, y: number) => x + y)
sumTupled([1, 2])// => 3untupled
Section titled “untupled”The inverse of tupled: adapts a function that takes one tuple back into a
multi-argument function.
import { Function } from "effect"
const getFirst = Function.untupled(<A, B>(tuple: [A, B]): A => tuple[0])
getFirst(1, 2)// => 1identity
Section titled “identity”Returns its input unchanged. Useful wherever a function is required but no transformation is wanted (e.g. a default mapping).
import { identity } from "effect"
identity(5)// => 5constant
Section titled “constant”Creates a zero-argument thunk that always returns the same provided value.
import { Function } from "effect"
const always42 = Function.constant(42)
always42() // => 42always42() // => 42constTrue
Section titled “constTrue”A thunk that always returns true. Equivalent to constant(true).
import { Function } from "effect"
Function.constTrue()// => trueconstFalse
Section titled “constFalse”A thunk that always returns false.
import { Function } from "effect"
Function.constFalse()// => falseconstNull
Section titled “constNull”A thunk that always returns null.
import { Function } from "effect"
Function.constNull()// => nullconstUndefined
Section titled “constUndefined”A thunk that always returns undefined.
import { Function } from "effect"
Function.constUndefined()// => undefinedconstVoid
Section titled “constVoid”A thunk used only for its call effect, returning no meaningful value (it is an
alias of constUndefined).
import { Function } from "effect"
Function.constVoid()// => undefinedThe SK combinator: takes two arguments and returns the second, discarding the first.
import { Function } from "effect"
Function.SK(0, "hello")// => "hello"satisfies
Section titled “satisfies”A type-level check that an expression is assignable to a type without widening or changing the expression’s inferred type. The value is returned unchanged at runtime.
import { Function } from "effect"
const x = Function.satisfies<number>()(5 as const)// ^? const x: 5 (still narrowed to the literal 5)
// @ts-expect-error - 5 is not assignable to stringFunction.satisfies<string>()(5)
x// => 5Returns the input value with a different static type. This is a type-level cast only — it performs no runtime validation or conversion. Reach for it only when you genuinely need to override the type checker.
import { Function } from "effect"
const value: unknown = "hello"const asString = Function.cast<unknown, string>(value)// ^? const asString: string (no runtime change)
asString// => "hello"absurd
Section titled “absurd”Marks an unreachable branch. It accepts a value of type never and returns any
type, which is useful for exhaustiveness proofs. Because a never value should
never exist at runtime, calling absurd throws.
import { absurd } from "effect"
type Shape = { _tag: "circle" } | { _tag: "square" }
const area = (shape: Shape): number => { switch (shape._tag) { case "circle": return 1 case "square": return 2 default: // If a new variant is added, this line becomes a type error, // proving the switch is exhaustive. return absurd(shape) }}
area({ _tag: "circle" })// => 1A compile-time placeholder of any type, for sketching incomplete code while
keeping the types happy. Like absurd, evaluating hole at runtime throws,
so it is strictly a development aid.
import { hole } from "effect"
// Types check, but calling `buildUser` would throw at the `hole` call.const buildUser = (id: number): { readonly id: number; readonly name: string } => ({ id, name: hole<string>() // TODO: implement})
typeof buildUser// => "function"LazyArg (type)
Section titled “LazyArg (type)”The type of a zero-argument function that produces a value lazily —
() => A. Used to type thunks that should not run until called.
import { Function } from "effect"
const lazyConfig: Function.LazyArg<{ readonly retries: number }> = () => ({ retries: 3})
lazyConfig()// => { retries: 3 }FunctionN (type)
Section titled “FunctionN (type)”Describes a function whose argument list is given as a tuple type and whose
return type is B.
import { Function } from "effect"
const sum: Function.FunctionN<[number, number], number> = (a, b) => a + b
sum(2, 3)// => 5FunctionTypeLambda (type)
Section titled “FunctionTypeLambda (type)”The higher-kinded type lambda for unary functions, used in advanced HKT-based
abstractions. You almost never reference this directly; it exists so generic
machinery can speak about (a) => b as a type constructor.
import type { Function, HKT } from "effect"
// Equivalent to `(a: string) => number`type StringToNumber = HKT.Kind< Function.FunctionTypeLambda, string, never, never, number>memoize
Section titled “memoize”Wraps a function whose input is an object, caching results by object
identity using a private WeakMap. Subsequent calls with the same reference
return the cached result without recomputing.
import { Function } from "effect"
let calls = 0const expensive = Function.memoize((config: { id: number }) => { calls++ return config.id * 2})
const config = { id: 21 }
expensive(config) // => 42 (computed, calls === 1)expensive(config) // => 42 (cached, calls still 1)
// A *different* object — even if structurally equal — is a cache miss:expensive({ id: 21 }) // => 42 (recomputed, calls === 2)Important caveats:
- The key must be an object (it is stored in a
WeakMap). It is not a general-purpose cache for primitive values. - Structurally equal objects do not share a cache entry — only identity matters.
- If an object is mutated after its first call, later calls still return the originally cached result for that reference.
For caching by value, with TTL, capacity limits, or effectful lookups, use the caching modules instead.