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 typeconst isPositive: Predicate.Predicate<number> = (n) => n > 0
// A Refinement<unknown, string>: narrows `unknown` to `string` on successconst 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 -> integerconst isIntegerLike = Predicate.compose(Predicate.isNumber, isInteger)
console.log(isIntegerLike(3)) // => trueconsole.log(isIntegerLike(3.5)) // => falseconsole.log(isIntegerLike("3")) // => falsePredicate
Section titled “Predicate”Predicate
Section titled “Predicate”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)) // => trueconsole.log(isEven(5)) // => falseRefinement
Section titled “Refinement”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)}PredicateTypeLambda
Section titled “PredicateTypeLambda”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.PredicateTypeLambdatype 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> // unknowntype Out = Predicate.Refinement.Out<R> // stringPrimitive type guards
Section titled “Primitive type guards”Each of these is a Refinement over unknown (unless noted) and narrows in
if/filter.
isString
Section titled “isString”Narrows unknown to string via typeof.
import { Predicate } from "effect"
const u: unknown = "hi"console.log(Predicate.isString(u)) // => trueconsole.log(Predicate.isString(42)) // => falseisNumber
Section titled “isNumber”Narrows to number. Does not exclude NaN or Infinity.
import { Predicate } from "effect"
console.log(Predicate.isNumber(42)) // => trueconsole.log(Predicate.isNumber(NaN)) // => trueconsole.log(Predicate.isNumber("42")) // => falseisBoolean
Section titled “isBoolean”Narrows to boolean.
import { Predicate } from "effect"
console.log(Predicate.isBoolean(true)) // => trueconsole.log(Predicate.isBoolean(0)) // => falseisBigInt
Section titled “isBigInt”Narrows to bigint.
import { Predicate } from "effect"
console.log(Predicate.isBigInt(1n)) // => trueconsole.log(Predicate.isBigInt(1)) // => falseisSymbol
Section titled “isSymbol”Narrows to symbol.
import { Predicate } from "effect"
console.log(Predicate.isSymbol(Symbol.for("id"))) // => trueconsole.log(Predicate.isSymbol("id")) // => falseisPropertyKey
Section titled “isPropertyKey”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"}isFunction
Section titled “isFunction”Narrows to Function.
import { Predicate } from "effect"
const f: unknown = () => 1console.log(Predicate.isFunction(f)) // => trueconsole.log(Predicate.isFunction({})) // => falseisUndefined
Section titled “isUndefined”Narrows to undefined (strict === undefined).
import { Predicate } from "effect"
console.log(Predicate.isUndefined(undefined)) // => trueconsole.log(Predicate.isUndefined(null)) // => falseisNotUndefined
Section titled “isNotUndefined”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]isNull
Section titled “isNull”Narrows to null (strict === null).
import { Predicate } from "effect"
console.log(Predicate.isNull(null)) // => trueconsole.log(Predicate.isNull(undefined)) // => falseisNotNull
Section titled “isNotNull”Excludes null: A becomes Exclude<A, null>.
import { Predicate } from "effect"
const values = [1, null, 2]console.log(values.filter(Predicate.isNotNull)) // => [1, 2]isNullish
Section titled “isNullish”Narrows to null | undefined (matches both).
import { Predicate } from "effect"
const values = [0, null, "", undefined]console.log(values.filter(Predicate.isNullish)) // => [null, undefined]isNotNullish
Section titled “isNotNullish”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, ""]isNever
Section titled “isNever”A refinement that always returns false (narrows to never). Useful for
exhaustiveness defaults.
import { Predicate } from "effect"
console.log(Predicate.isNever("anything")) // => falseisUnknown
Section titled “isUnknown”A refinement that always returns true. Useful as a placeholder predicate.
import { Predicate } from "effect"
console.log(Predicate.isUnknown(123)) // => trueisObject
Section titled “isObject”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 })) // => trueconsole.log(Predicate.isObject([1, 2])) // => false (arrays excluded)console.log(Predicate.isObject(null)) // => falseisObjectOrArray
Section titled “isObjectOrArray”Narrows to a non-null object or array (any typeof === "object" that is not
null).
import { Predicate } from "effect"
console.log(Predicate.isObjectOrArray([])) // => trueconsole.log(Predicate.isObjectOrArray({ a: 1 })) // => trueconsole.log(Predicate.isObjectOrArray(null)) // => falseisReadonlyObject
Section titled “isReadonlyObject”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)) // => trueisObjectKeyword
Section titled “isObjectKeyword”Narrows to the TypeScript object keyword — accepts objects, arrays, and
functions, but not null.
import { Predicate } from "effect"
console.log(Predicate.isObjectKeyword(() => 1)) // => trueconsole.log(Predicate.isObjectKeyword([])) // => trueconsole.log(Predicate.isObjectKeyword(null)) // => falseisTruthy
Section titled “isTruthy”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}isError
Section titled “isError”Narrows to Error via instanceof.
import { Predicate } from "effect"
console.log(Predicate.isError(new Error("boom"))) // => trueconsole.log(Predicate.isError("boom")) // => falseisUint8Array
Section titled “isUint8Array”Narrows to Uint8Array via instanceof.
import { Predicate } from "effect"
console.log(Predicate.isUint8Array(new Uint8Array([1, 2]))) // => trueconsole.log(Predicate.isUint8Array([1, 2])) // => falseisDate
Section titled “isDate”Narrows to Date via instanceof.
import { Predicate } from "effect"
console.log(Predicate.isDate(new Date())) // => trueconsole.log(Predicate.isDate("2026-01-01")) // => falseisIterable
Section titled “isIterable”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])) // => trueconsole.log(Predicate.isIterable("abc")) // => true (strings are iterable)console.log(Predicate.isIterable(42)) // => falseisPromise
Section titled “isPromise”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))) // => trueconsole.log(Predicate.isPromise({ then() {} })) // => false (no `catch`)isPromiseLike
Section titled “isPromiseLike”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() {} })) // => trueconsole.log(Predicate.isPromiseLike(42)) // => falseisRegExp
Section titled “isRegExp”Narrows to RegExp via instanceof.
import { Predicate } from "effect"
console.log(Predicate.isRegExp(/abc/)) // => trueconsole.log(Predicate.isRegExp("abc")) // => falseStructural guards
Section titled “Structural guards”hasProperty
Section titled “hasProperty”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)}isTagged
Section titled “isTagged”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 })) // => trueconsole.log(isOk({ _tag: "Err" })) // => falseisTupleOf
Section titled “isTupleOf”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])) // => trueconsole.log(isPair([1, 2, 3])) // => falseisTupleOfAtLeast
Section titled “isTupleOfAtLeast”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])) // => trueconsole.log(hasAtLeast2([1])) // => falseLifts 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"])) // => trueconsole.log(isPoint([1, 2])) // => falseStruct
Section titled “Struct”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" })) // => trueconsole.log(isUser({ id: "1", name: "Ada" })) // => falseCombinators
Section titled “Combinators”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.
mapInput
Section titled “mapInput”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")) // => trueconsole.log(longerThan2("hi")) // => falsecompose
Section titled “compose”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)) // => trueconsole.log(isIntegerLike(3.5)) // => falseNegates a predicate.
import { Predicate } from "effect"
const isNotString = Predicate.not(Predicate.isString)
console.log(isNotString(1)) // => trueconsole.log(isNotString("a")) // => falsetrue 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)) // => trueconsole.log(isPositiveInt(-3)) // => falsetrue 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")) // => trueconsole.log(isStringOrNumber(true)) // => falsetrue when exactly one of the two predicates passes.
import { Predicate } from "effect"
const isEven = (n: number) => n % 2 === 0const isPositive = (n: number) => n > 0const 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 === 0const same = Predicate.eqv(isEven, isEven)
console.log(same(3)) // => true (both false)console.log(same(4)) // => true (both true)implies
Section titled “implies”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 >= 18const canVote = (age: number) => age >= 18const rule = Predicate.implies(isAdult, canVote)
console.log(rule(16)) // => true (antecedent false)console.log(rule(20)) // => truetrue only when neither predicate passes (negation of or).
import { Predicate } from "effect"
const neither = Predicate.nor(Predicate.isString, Predicate.isNumber)
console.log(neither(true)) // => trueconsole.log(neither("a")) // => falsetrue unless both predicates pass (negation of and).
import { Predicate } from "effect"
const notBoth = Predicate.nand(Predicate.isString, Predicate.isNumber)
console.log(notBoth("a")) // => trueBuilds 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)) // => trueconsole.log(allChecks(-2)) // => falseBuilds 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")) // => trueconsole.log(anyCheck(true)) // => falseBoolean
Section titled “Boolean”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.
Boolean
Section titled “Boolean”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)) // => trueconsole.log(Boolean.Boolean("false")) // => true (non-empty string)console.log(Boolean.Boolean(0)) // => falseisBoolean
Section titled “isBoolean”Re-exports Predicate.isBoolean: narrows unknown to boolean.
import { Boolean } from "effect"
console.log(Boolean.isBoolean(true)) // => trueconsole.log(Boolean.isBoolean("true")) // => falseAn 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)) // => -1console.log(Boolean.Order(true, false)) // => 1console.log(Boolean.Order(true, true)) // => 0Equivalence
Section titled “Equivalence”An Equivalence<boolean> using strict equality.
import { Boolean } from "effect"
console.log(Boolean.Equivalence(true, true)) // => trueconsole.log(Boolean.Equivalence(true, false)) // => falseA 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)) // => falseconsole.log(Boolean.not(false)) // => trueLogical AND: self && that.
import { Boolean } from "effect"
console.log(Boolean.and(true, true)) // => trueconsole.log(Boolean.and(true, false)) // => falseNegated AND: !(self && that).
import { Boolean } from "effect"
console.log(Boolean.nand(true, true)) // => falseconsole.log(Boolean.nand(true, false)) // => trueconsole.log(Boolean.nand(false, false)) // => trueLogical OR: self || that.
import { Boolean } from "effect"
console.log(Boolean.or(false, true)) // => trueconsole.log(Boolean.or(false, false)) // => falseNegated OR — true only when both are false: !(self || that).
import { Boolean } from "effect"
console.log(Boolean.nor(false, false)) // => trueconsole.log(Boolean.nor(true, false)) // => falseExclusive OR — true when exactly one operand is true.
import { Boolean } from "effect"
console.log(Boolean.xor(true, false)) // => trueconsole.log(Boolean.xor(true, true)) // => falseconsole.log(Boolean.xor(false, false)) // => falseEquivalence (XNOR) — true when both operands have the same value: !xor.
import { Boolean } from "effect"
console.log(Boolean.eqv(true, true)) // => trueconsole.log(Boolean.eqv(false, false)) // => trueconsole.log(Boolean.eqv(true, false)) // => falseimplies
Section titled “implies”Logical implication: !self || that. true whenever self is false.
import { Boolean } from "effect"
console.log(Boolean.implies(true, true)) // => trueconsole.log(Boolean.implies(true, false)) // => falseconsole.log(Boolean.implies(false, false)) // => truetrue 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])) // => trueconsole.log(Boolean.every([true, false, true])) // => falseconsole.log(Boolean.every([])) // => truetrue 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])) // => trueconsole.log(Boolean.some([false, false, false])) // => falseconsole.log(Boolean.some([])) // => falseReducerAnd
Section titled “ReducerAnd”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])) // => falseconsole.log(Boolean.ReducerAnd.combineAll([])) // => trueReducerOr
Section titled “ReducerOr”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])) // => trueconsole.log(Boolean.ReducerOr.combineAll([])) // => false