Skip to content

Predicates & Booleans

A predicate is just a function from a value to a boolean: (a: A) => boolean. A refinement is a predicate that also narrows the TypeScript type when it returns true: (a: A) => a is B. The Predicate module gives you a library of built-in guards (for strings, numbers, objects, dates, promises, and more) plus combinators (and, or, not, compose, Struct, Tuple) that build bigger checks from smaller ones — crucially, while preserving narrowing where it makes sense. The Boolean module is the companion algebra for the booleans those predicates produce: named logical operators, a tiny branch helper (match), and collection reducers.

import { Predicate } from "effect"
// A Predicate<number>: returns boolean, does NOT narrow the type
const isPositive: Predicate.Predicate<number> = (n) => n > 0
// A Refinement<unknown, string>: narrows `unknown` to `string` on success
const isString: Predicate.Refinement<unknown, string> = (u): u is string =>
typeof u === "string"
const value: unknown = "hello"
if (isString(value)) {
// TypeScript now knows `value` is a string here
console.log(value.toUpperCase()) // => "HELLO"
}

Common case: compose guards while keeping narrowing

Section titled “Common case: compose guards while keeping narrowing”

The whole point of the module is that combinators keep their refinement information. Combine the built-in guards to validate and narrow an unknown value, then keep the narrowed type:

import { Predicate } from "effect"
// isObject narrows to a record; hasProperty narrows to "has key K"
const isUser = Predicate.and(
Predicate.hasProperty("name"),
Predicate.hasProperty("age")
)
const input: unknown = JSON.parse(`{ "name": "Ada", "age": 36 }`)
if (isUser(input)) {
// input is narrowed to { name: unknown } & { age: unknown }
console.log(input.name, input.age) // => Ada 36
}

Because guards are plain functions, they drop straight into native Array.prototype.filter, and the result is narrowed too:

import { Predicate } from "effect"
const values: Array<unknown> = ["one", 2, "three", null, 4]
// strings: string[] (filter narrows because isString is a Refinement)
const strings = values.filter(Predicate.isString)
console.log(strings) // => ["one", "three"]
// numbers: number[]
const numbers = values.filter(Predicate.isNumber)
console.log(numbers) // => [2, 4]

To narrow in stages (e.g. first check it is a number, then that it is an integer), use compose instead of and — the second refinement is applied to the already-narrowed type:

import { Predicate } from "effect"
const isInteger: Predicate.Refinement<number, number> = (n): n is number =>
Number.isInteger(n)
// unknown -> number -> integer
const isIntegerLike = Predicate.compose(Predicate.isNumber, isInteger)
console.log(isIntegerLike(3)) // => true
console.log(isIntegerLike(3.5)) // => false
console.log(isIntegerLike("3")) // => false

Predicate<A> is the interface for a plain boolean check: (a: A) => boolean. It never narrows on its own.

import { Predicate } from "effect"
const isEven: Predicate.Predicate<number> = (n) => n % 2 === 0
console.log(isEven(4)) // => true
console.log(isEven(5)) // => false

Refinement<A, B extends A> is (a: A) => a is B: a predicate that narrows A to B for the TypeScript compiler when it returns true. Every built-in is* guard is a Refinement.

import { Predicate } from "effect"
const isString: Predicate.Refinement<unknown, string> = (u): u is string =>
typeof u === "string"
const x: unknown = "hi"
if (isString(x)) {
console.log(x.length) // => 2 (x is string here)
}

The higher-kinded TypeLambda for Predicate, used when defining type-class instances that abstract over predicates. Type-only; produces no runtime value.

import { Predicate } from "effect"
type TL = Predicate.PredicateTypeLambda
type P = Predicate.Predicate<number>

There are also type-level helpers under the Predicate and Refinement namespaces for generic code: Predicate.In<T> / Predicate.Any, and Refinement.In<T> / Refinement.Out<T> / Refinement.Any.

import { Predicate } from "effect"
type R = Predicate.Refinement<unknown, string>
type In = Predicate.Refinement.In<R> // unknown
type Out = Predicate.Refinement.Out<R> // string

Each of these is a Refinement over unknown (unless noted) and narrows in if/filter.

Narrows unknown to string via typeof.

import { Predicate } from "effect"
const u: unknown = "hi"
console.log(Predicate.isString(u)) // => true
console.log(Predicate.isString(42)) // => false

Narrows to number. Does not exclude NaN or Infinity.

import { Predicate } from "effect"
console.log(Predicate.isNumber(42)) // => true
console.log(Predicate.isNumber(NaN)) // => true
console.log(Predicate.isNumber("42")) // => false

Narrows to boolean.

import { Predicate } from "effect"
console.log(Predicate.isBoolean(true)) // => true
console.log(Predicate.isBoolean(0)) // => false

Narrows to bigint.

import { Predicate } from "effect"
console.log(Predicate.isBigInt(1n)) // => true
console.log(Predicate.isBigInt(1)) // => false

Narrows to symbol.

import { Predicate } from "effect"
console.log(Predicate.isSymbol(Symbol.for("id"))) // => true
console.log(Predicate.isSymbol("id")) // => false

Narrows to PropertyKey (a string, number, or symbol) — handy before indexing into an object.

import { Predicate } from "effect"
const obj: Record<PropertyKey, unknown> = { name: "Ada" }
const key: unknown = "name"
if (Predicate.isPropertyKey(key) && key in obj) {
console.log(obj[key]) // => "Ada"
}

Narrows to Function.

import { Predicate } from "effect"
const f: unknown = () => 1
console.log(Predicate.isFunction(f)) // => true
console.log(Predicate.isFunction({})) // => false

Narrows to undefined (strict === undefined).

import { Predicate } from "effect"
console.log(Predicate.isUndefined(undefined)) // => true
console.log(Predicate.isUndefined(null)) // => false

Excludes undefined from the type: A becomes Exclude<A, undefined>. Keeps other falsy values.

import { Predicate } from "effect"
const values = [1, undefined, 2]
console.log(values.filter(Predicate.isNotUndefined)) // => [1, 2]

Narrows to null (strict === null).

import { Predicate } from "effect"
console.log(Predicate.isNull(null)) // => true
console.log(Predicate.isNull(undefined)) // => false

Excludes null: A becomes Exclude<A, null>.

import { Predicate } from "effect"
const values = [1, null, 2]
console.log(values.filter(Predicate.isNotNull)) // => [1, 2]

Narrows to null | undefined (matches both).

import { Predicate } from "effect"
const values = [0, null, "", undefined]
console.log(values.filter(Predicate.isNullish)) // => [null, undefined]

Narrows to NonNullable<A> — removes both null and undefined but keeps other falsy values like 0 and "".

import { Predicate } from "effect"
const values = [0, null, "", undefined]
console.log(values.filter(Predicate.isNotNullish)) // => [0, ""]

A refinement that always returns false (narrows to never). Useful for exhaustiveness defaults.

import { Predicate } from "effect"
console.log(Predicate.isNever("anything")) // => false

A refinement that always returns true. Useful as a placeholder predicate.

import { Predicate } from "effect"
console.log(Predicate.isUnknown(123)) // => true

Narrows to a non-null object that is not an array. Structural typeof check, so it also accepts Date, Map, and class instances.

import { Predicate } from "effect"
console.log(Predicate.isObject({ a: 1 })) // => true
console.log(Predicate.isObject([1, 2])) // => false (arrays excluded)
console.log(Predicate.isObject(null)) // => false

Narrows to a non-null object or array (any typeof === "object" that is not null).

import { Predicate } from "effect"
console.log(Predicate.isObjectOrArray([])) // => true
console.log(Predicate.isObjectOrArray({ a: 1 })) // => true
console.log(Predicate.isObjectOrArray(null)) // => false

Same runtime check as isObject, but narrows to a readonly indexable object type. Readonly-ness is type-level only.

import { Predicate } from "effect"
const data: unknown = { a: 1 }
console.log(Predicate.isReadonlyObject(data)) // => true

Narrows to the TypeScript object keyword — accepts objects, arrays, and functions, but not null.

import { Predicate } from "effect"
console.log(Predicate.isObjectKeyword(() => 1)) // => true
console.log(Predicate.isObjectKeyword([])) // => true
console.log(Predicate.isObjectKeyword(null)) // => false

JavaScript truthiness via !!input, so 0, "", false, null, and undefined are rejected. Does not narrow to a useful type.

import { Predicate } from "effect"
console.log([0, 1, "", "ok", false].filter(Predicate.isTruthy)) // => [1, "ok"]

Narrows to Set<unknown> via instanceof.

import { Predicate } from "effect"
const data: unknown = new Set([1, 2])
if (Predicate.isSet(data)) {
console.log(data.size) // => 2
}

Narrows to Map<unknown, unknown> via instanceof.

import { Predicate } from "effect"
const data: unknown = new Map([["a", 1]])
if (Predicate.isMap(data)) {
console.log(data.size) // => 1
}

Narrows to Error via instanceof.

import { Predicate } from "effect"
console.log(Predicate.isError(new Error("boom"))) // => true
console.log(Predicate.isError("boom")) // => false

Narrows to Uint8Array via instanceof.

import { Predicate } from "effect"
console.log(Predicate.isUint8Array(new Uint8Array([1, 2]))) // => true
console.log(Predicate.isUint8Array([1, 2])) // => false

Narrows to Date via instanceof.

import { Predicate } from "effect"
console.log(Predicate.isDate(new Date())) // => true
console.log(Predicate.isDate("2026-01-01")) // => false

Narrows to Iterable<unknown> by checking for Symbol.iterator. Note that strings are iterable, so they pass.

import { Predicate } from "effect"
console.log(Predicate.isIterable([1, 2, 3])) // => true
console.log(Predicate.isIterable("abc")) // => true (strings are iterable)
console.log(Predicate.isIterable(42)) // => false

Structural check (not instanceof): narrows to Promise<unknown> when the value has callable then and catch methods.

import { Predicate } from "effect"
console.log(Predicate.isPromise(Promise.resolve(1))) // => true
console.log(Predicate.isPromise({ then() {} })) // => false (no `catch`)

Narrows to PromiseLike<unknown> when the value has a callable then method (does not require catch).

import { Predicate } from "effect"
console.log(Predicate.isPromiseLike({ then() {} })) // => true
console.log(Predicate.isPromiseLike(42)) // => false

Narrows to RegExp via instanceof.

import { Predicate } from "effect"
console.log(Predicate.isRegExp(/abc/)) // => true
console.log(Predicate.isRegExp("abc")) // => false

Narrows an unknown to “has property K” via the in operator. Does not check the value’s type. Data-first and data-last.

import { Predicate } from "effect"
const hasName = Predicate.hasProperty("name")
const data: unknown = { name: "Ada" }
if (hasName(data)) {
console.log(data.name) // => "Ada" (typed as unknown)
}

Narrows to { _tag: K } — a quick structural guard for tagged unions that use a _tag discriminant.

import { Predicate } from "effect"
const isOk = Predicate.isTagged("Ok")
console.log(isOk({ _tag: "Ok", value: 1 })) // => true
console.log(isOk({ _tag: "Err" })) // => false

Refines a ReadonlyArray<T> to a tuple of exactly N elements. Checks length only, not element types. Data-first and data-last.

import { Predicate } from "effect"
const isPair = Predicate.isTupleOf(2)
console.log(isPair([1, 2])) // => true
console.log(isPair([1, 2, 3])) // => false

Refines a ReadonlyArray<T> to a tuple of at least N elements (length check only).

import { Predicate } from "effect"
const hasAtLeast2 = Predicate.isTupleOfAtLeast(2)
console.log(hasAtLeast2([1, 2, 3])) // => true
console.log(hasAtLeast2([1])) // => false

Lifts an array of element predicates into a single tuple predicate, checking each position independently. If any element is a refinement, the result is a refinement that narrows the whole tuple.

import { Predicate } from "effect"
const isPoint = Predicate.Tuple([Predicate.isNumber, Predicate.isString])
console.log(isPoint([1, "ok"])) // => true
console.log(isPoint([1, 2])) // => false

Lifts a record of property predicates into a single object predicate, checking only the named keys (extra keys are ignored). If any field is a refinement, the result narrows the whole object.

import { Predicate } from "effect"
const isUser = Predicate.Struct({
id: Predicate.isNumber,
name: Predicate.isString
})
console.log(isUser({ id: 1, name: "Ada" })) // => true
console.log(isUser({ id: "1", name: "Ada" })) // => false

and, or, compose, Tuple, and Struct preserve refinement narrowing: combine refinements and you still get a refinement (and intersects the narrowed types, or unions them, compose chains them). The remaining combinators (not, xor, eqv, implies, nor, nand, every, some) operate on plain Predicates and return plain Predicates — they do not narrow.

Adapts a Predicate<A> to a Predicate<B> by mapping B -> A before the check. Data-first and data-last.

import { Predicate } from "effect"
const longerThan2 = Predicate.mapInput(
(n: number) => n > 2,
(s: string) => s.length
)
console.log(longerThan2("hello")) // => true
console.log(longerThan2("hi")) // => false

Chains two refinements so the second narrows the result of the first (A -> B -> C). The second argument may also be a plain Predicate<B>, in which case the result is Refinement<A, B>. Short-circuits on the first false.

import { Predicate } from "effect"
const isInteger: Predicate.Refinement<number, number> = (n): n is number =>
Number.isInteger(n)
const isIntegerLike = Predicate.compose(Predicate.isNumber, isInteger)
console.log(isIntegerLike(3)) // => true
console.log(isIntegerLike(3.5)) // => false

Negates a predicate.

import { Predicate } from "effect"
const isNotString = Predicate.not(Predicate.isString)
console.log(isNotString(1)) // => true
console.log(isNotString("a")) // => false

true only when both pass. For refinements, the output is the intersection of both narrowed types. Short-circuits on the first false. Data-first and data-last.

import { Predicate } from "effect"
const isPositiveInt = Predicate.and(
Predicate.isNumber,
(n: number) => Number.isInteger(n) && n > 0
)
console.log(isPositiveInt(3)) // => true
console.log(isPositiveInt(-3)) // => false

true when either passes. For refinements, the output is the union of both narrowed types. Short-circuits on the first true.

import { Predicate } from "effect"
// Refinement<unknown, string | number>
const isStringOrNumber = Predicate.or(Predicate.isString, Predicate.isNumber)
console.log(isStringOrNumber("a")) // => true
console.log(isStringOrNumber(true)) // => false

true when exactly one of the two predicates passes.

import { Predicate } from "effect"
const isEven = (n: number) => n % 2 === 0
const isPositive = (n: number) => n > 0
const either = Predicate.xor(isEven, isPositive)
console.log(either(-2)) // => true (even but not positive)
console.log(either(4)) // => false (both)

true when both predicates agree (both true or both false).

import { Predicate } from "effect"
const isEven = (n: number) => n % 2 === 0
const same = Predicate.eqv(isEven, isEven)
console.log(same(3)) // => true (both false)
console.log(same(4)) // => true (both true)

Logical implication: if the antecedent holds, the consequent must too. Returns true whenever the antecedent is false.

import { Predicate } from "effect"
const isAdult = (age: number) => age >= 18
const canVote = (age: number) => age >= 18
const rule = Predicate.implies(isAdult, canVote)
console.log(rule(16)) // => true (antecedent false)
console.log(rule(20)) // => true

true only when neither predicate passes (negation of or).

import { Predicate } from "effect"
const neither = Predicate.nor(Predicate.isString, Predicate.isNumber)
console.log(neither(true)) // => true
console.log(neither("a")) // => false

true unless both predicates pass (negation of and).

import { Predicate } from "effect"
const notBoth = Predicate.nand(Predicate.isString, Predicate.isNumber)
console.log(notBoth("a")) // => true

Builds a predicate from a collection of predicates that passes only when all of them pass. Short-circuits on the first false; re-iterates the collection on each call.

import { Predicate } from "effect"
const allChecks = Predicate.every([Predicate.isNumber, (n: number) => n > 0])
console.log(allChecks(2)) // => true
console.log(allChecks(-2)) // => false

Builds a predicate that passes when any predicate in the collection passes. Short-circuits on the first true.

import { Predicate } from "effect"
const anyCheck = Predicate.some([Predicate.isString, Predicate.isNumber])
console.log(anyCheck("ok")) // => true
console.log(anyCheck(true)) // => false

The Boolean module is the algebra for the booleans your predicates produce: named logical operators, a branch helper, ordering/equivalence instances, and reducers. The combinators are data-first and data-last via dual.

A re-export of the native globalThis.Boolean constructor for truthiness coercion. Follows JavaScript truthiness, so non-empty strings like "false" coerce to true.

import { Boolean } from "effect"
console.log(Boolean.Boolean(1)) // => true
console.log(Boolean.Boolean("false")) // => true (non-empty string)
console.log(Boolean.Boolean(0)) // => false

Re-exports Predicate.isBoolean: narrows unknown to boolean.

import { Boolean } from "effect"
console.log(Boolean.isBoolean(true)) // => true
console.log(Boolean.isBoolean("true")) // => false

An Order<boolean> instance where false < true. Pass it to APIs that sort or compare via an ordering.

import { Boolean } from "effect"
console.log(Boolean.Order(false, true)) // => -1
console.log(Boolean.Order(true, false)) // => 1
console.log(Boolean.Order(true, true)) // => 0

An Equivalence<boolean> using strict equality.

import { Boolean } from "effect"
console.log(Boolean.Equivalence(true, true)) // => true
console.log(Boolean.Equivalence(true, false)) // => false

A tiny branch helper: picks between two lazy branches based on a boolean. Cleaner than an inline ternary when both arms are expressions. Data-first and data-last.

import { Boolean } from "effect"
const message = Boolean.match(true, {
onFalse: () => "It's false!",
onTrue: () => "It's true!"
})
console.log(message) // => "It's true!"

Negates a boolean: !self.

import { Boolean } from "effect"
console.log(Boolean.not(true)) // => false
console.log(Boolean.not(false)) // => true

Logical AND: self && that.

import { Boolean } from "effect"
console.log(Boolean.and(true, true)) // => true
console.log(Boolean.and(true, false)) // => false

Negated AND: !(self && that).

import { Boolean } from "effect"
console.log(Boolean.nand(true, true)) // => false
console.log(Boolean.nand(true, false)) // => true
console.log(Boolean.nand(false, false)) // => true

Logical OR: self || that.

import { Boolean } from "effect"
console.log(Boolean.or(false, true)) // => true
console.log(Boolean.or(false, false)) // => false

Negated OR — true only when both are false: !(self || that).

import { Boolean } from "effect"
console.log(Boolean.nor(false, false)) // => true
console.log(Boolean.nor(true, false)) // => false

Exclusive OR — true when exactly one operand is true.

import { Boolean } from "effect"
console.log(Boolean.xor(true, false)) // => true
console.log(Boolean.xor(true, true)) // => false
console.log(Boolean.xor(false, false)) // => false

Equivalence (XNOR) — true when both operands have the same value: !xor.

import { Boolean } from "effect"
console.log(Boolean.eqv(true, true)) // => true
console.log(Boolean.eqv(false, false)) // => true
console.log(Boolean.eqv(true, false)) // => false

Logical implication: !self || that. true whenever self is false.

import { Boolean } from "effect"
console.log(Boolean.implies(true, true)) // => true
console.log(Boolean.implies(true, false)) // => false
console.log(Boolean.implies(false, false)) // => true

true when every boolean in an iterable is true. Returns true for an empty iterable (matching logical AND).

import { Boolean } from "effect"
console.log(Boolean.every([true, true, true])) // => true
console.log(Boolean.every([true, false, true])) // => false
console.log(Boolean.every([])) // => true

true when at least one boolean in an iterable is true. Returns false for an empty iterable (matching logical OR).

import { Boolean } from "effect"
console.log(Boolean.some([true, false, true])) // => true
console.log(Boolean.some([false, false, false])) // => false
console.log(Boolean.some([])) // => false

A Reducer<boolean> that combines with AND (initialValue: true). Pass it to generic APIs that consume a Reducer; for direct iterable checks prefer every. Note combineAll folds left-to-right and does not short-circuit on false.

import { Boolean } from "effect"
console.log(Boolean.ReducerAnd.combineAll([true, true, false])) // => false
console.log(Boolean.ReducerAnd.combineAll([])) // => true

A Reducer<boolean> that combines with OR (initialValue: false). For direct iterable checks prefer some.

import { Boolean } from "effect"
console.log(Boolean.ReducerOr.combineAll([false, false, true])) // => true
console.log(Boolean.ReducerOr.combineAll([])) // => false