Skip to content

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"

pipe and flow are two halves of the same idea — feeding a value through a left-to-right sequence of unary functions.

  • pipe starts with a value and applies functions to it immediately, returning the final result.
  • flow composes 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 now
const result = pipe(
5,
(n) => n + 1, // 6
(n) => n * 2, // 12
(n) => `value: ${n}`
)
// => "value: 12"
// `flow`: compose the same steps into a reusable function
const 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.

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.

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.


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)
// => 12

Composes 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")
// => 6

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 + 1
const 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"))
// => 5

Builds 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) // => 5
pipe(2, sum(3)) // => 5

See /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)

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])
// => 3

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)
// => 1

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)
// => 5

Creates a zero-argument thunk that always returns the same provided value.

import { Function } from "effect"
const always42 = Function.constant(42)
always42() // => 42
always42() // => 42

A thunk that always returns true. Equivalent to constant(true).

import { Function } from "effect"
Function.constTrue()
// => true

A thunk that always returns false.

import { Function } from "effect"
Function.constFalse()
// => false

A thunk that always returns null.

import { Function } from "effect"
Function.constNull()
// => null

A thunk that always returns undefined.

import { Function } from "effect"
Function.constUndefined()
// => undefined

A thunk used only for its call effect, returning no meaningful value (it is an alias of constUndefined).

import { Function } from "effect"
Function.constVoid()
// => undefined

The SK combinator: takes two arguments and returns the second, discarding the first.

import { Function } from "effect"
Function.SK(0, "hello")
// => "hello"

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 string
Function.satisfies<string>()(5)
x
// => 5

Returns 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"

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" })
// => 1

A 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"

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 }

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)
// => 5

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
>

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 = 0
const 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.