Skip to main content Link Search Menu Expand Document (external link)

Match.ts overview

The effect/match module provides a type-safe pattern matching system for TypeScript. Inspired by functional programming, it simplifies conditional logic by replacing verbose if/else or switch statements with a structured and expressive API.

This module supports matching against types, values, and discriminated unions while enforcing exhaustiveness checking to ensure all cases are handled.

Although pattern matching is not yet a native JavaScript feature, effect/match offers a reliable implementation that is available today.

How Pattern Matching Works

Pattern matching follows a structured process:

  • Creating a matcher: Define a Matcher that operates on either a specific Match.type or Match.value.

  • Defining patterns: Use combinators such as Match.when, Match.not, and Match.tag to specify matching conditions.

  • Completing the match: Apply a finalizer such as Match.exhaustive, Match.orElse, or Match.option to determine how unmatched cases should be handled.

Since v1.0.0


Exports Grouped by Category


Completion

either

Wraps the match result in an Either, distinguishing matched and unmatched cases.

Details

This function ensures that the result of a matcher is always wrapped in an Either, allowing clear differentiation between successful matches (Right(value)) and cases where no pattern matched (Left(unmatched value)).

This approach is particularly useful when handling optional values or when an unmatched case should be explicitly handled rather than returning a default value or throwing an error.

Example (Extracting a User Role with Match.either)

import { Match } from "effect"

type User = { readonly role: "admin" | "editor" | "viewer" }

// Create a matcher to extract user roles
const getRole = Match.type<User>().pipe(
  Match.when({ role: "admin" }, () => "Has full access"),
  Match.when({ role: "editor" }, () => "Can edit content"),
  Match.either // Wrap the result in an Either
)

console.log(getRole({ role: "admin" }))
// Output: { _id: 'Either', _tag: 'Right', right: 'Has full access' }

console.log(getRole({ role: "viewer" }))
// Output: { _id: 'Either', _tag: 'Left', left: { role: 'viewer' } }

Signature

declare const either: <I, F, R, A, Pr, Ret>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => [Pr] extends [never] ? (input: I) => Either.Either<Unify<A>, R> : Either.Either<Unify<A>, R>

Source

Since v1.0.0

exhaustive

The Match.exhaustive method finalizes the pattern matching process by ensuring that all possible cases are accounted for. If any case is missing, TypeScript will produce a type error. This is particularly useful when working with unions, as it helps prevent unintended gaps in pattern matching.

Example (Ensuring All Cases Are Covered)

import { Match } from "effect"

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match when the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Mark the match as exhaustive, ensuring all cases are handled
  // TypeScript will throw an error if any case is missing
  // @ts-expect-error Type 'string' is not assignable to type 'never'
  Match.exhaustive
)

Signature

declare const exhaustive: <I, F, A, Pr, Ret>(
  self: Matcher<I, F, never, A, Pr, Ret>
) => [Pr] extends [never] ? (u: I) => Unify<A> : Unify<A>

Source

Since v1.0.0

option

Wraps the match result in an Option, representing an optional match.

Details

This function ensures that the result of a matcher is wrapped in an Option, making it easy to handle cases where no pattern matches. If a match is found, it returns Some(value), otherwise, it returns None.

This is useful in cases where a missing match is expected and should be handled explicitly rather than throwing an error or returning a default value.

Example (Extracting a User Role with Match.option)

import { Match } from "effect"

type User = { readonly role: "admin" | "editor" | "viewer" }

// Create a matcher to extract user roles
const getRole = Match.type<User>().pipe(
  Match.when({ role: "admin" }, () => "Has full access"),
  Match.when({ role: "editor" }, () => "Can edit content"),
  Match.option // Wrap the result in an Option
)

console.log(getRole({ role: "admin" }))
// Output: { _id: 'Option', _tag: 'Some', value: 'Has full access' }

console.log(getRole({ role: "viewer" }))
// Output: { _id: 'Option', _tag: 'None' }

Signature

declare const option: <I, F, R, A, Pr, Ret>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => [Pr] extends [never] ? (input: I) => Option.Option<Unify<A>> : Option.Option<Unify<A>>

Source

Since v1.0.0

orElse

Provides a fallback value when no patterns match.

Details

This function ensures that a matcher always returns a valid result, even if no defined patterns match. It acts as a default case, similar to the default clause in a switch statement or the final else in an if-else chain.

Example (Providing a Default Value When No Patterns Match)

import { Match } from "effect"

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match when the value is "a"
  Match.when("a", () => "ok"),
  // Fallback when no patterns match
  Match.orElse(() => "fallback")
)

console.log(match("a"))
// Output: "ok"

console.log(match("b"))
// Output: "fallback"

Signature

declare const orElse: <RA, Ret, F extends (_: RA) => Ret>(
  f: F
) => <I, R, A, Pr>(
  self: Matcher<I, R, RA, A, Pr, Ret>
) => [Pr] extends [never] ? (input: I) => Unify<ReturnType<F> | A> : Unify<ReturnType<F> | A>

Source

Since v1.0.0

orElseAbsurd

Throws an error if no pattern matches.

Details

This function finalizes a matcher by ensuring that if no patterns match, an error is thrown. It is useful when all cases should be covered, and any unexpected input should trigger an error instead of returning a default value.

When used, this function removes the need for an explicit fallback case and ensures that an unmatched value is never silently ignored.

Signature

declare const orElseAbsurd: <I, R, RA, A, Pr, Ret>(
  self: Matcher<I, R, RA, A, Pr, Ret>
) => [Pr] extends [never] ? (input: I) => Unify<A> : Unify<A>

Source

Since v1.0.0

Creating a matcher

type

Creates a matcher for a specific type.

Details

This function defines a Matcher that operates on a given type, allowing you to specify conditions for handling different cases. Once the matcher is created, you can use pattern-matching functions like when to define how different values should be processed.

Example (Matching Numbers and Strings)

import { Match } from "effect"

// Create a matcher for values that are either strings or numbers
//
//      ┌─── (u: string | number) => string
//      ▼
const match = Match.type<string | number>().pipe(
  // Match when the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Match when the value is a string
  Match.when(Match.string, (s) => `string: ${s}`),
  // Ensure all possible cases are handled
  Match.exhaustive
)

console.log(match(0))
// Output: "number: 0"

console.log(match("hello"))
// Output: "string: hello"

See

  • value for creating a matcher from a specific value.

Signature

declare const type: <I>() => Matcher<I, Types.Without<never>, I, never, never>

Source

Since v1.0.0

typeTags

Signature

declare const typeTags: <I>() => <
  P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any } & {
    readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never
  }
>(
  fields: P
) => (input: I) => Unify<ReturnType<P[keyof P]>>

Source

Since v1.0.0

value

Creates a matcher from a specific value.

Details

This function allows you to define a Matcher directly from a given value, rather than from a type. This is useful when working with known values, enabling structured pattern matching on objects, primitives, or any data structure.

Once the matcher is created, you can use pattern-matching functions like when to define how different cases should be handled.

Example (Matching an Object by Property)

import { Match } from "effect"

const input = { name: "John", age: 30 }

// Create a matcher for the specific object
const result = Match.value(input).pipe(
  // Match when the 'name' property is "John"
  Match.when({ name: "John" }, (user) => `${user.name} is ${user.age} years old`),
  // Provide a fallback if no match is found
  Match.orElse(() => "Oh, not John")
)

console.log(result)
// Output: "John is 30 years old"

See

  • type for creating a matcher from a specific type.

Signature

declare const value: <const I>(i: I) => Matcher<I, Types.Without<never>, I, never, I>

Source

Since v1.0.0

valueTags

Signature

declare const valueTags: <
  const I,
  P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any } & {
    readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never
  }
>(
  fields: P
) => (input: I) => Unify<ReturnType<P[keyof P]>>

Source

Since v1.0.0

Defining patterns

discriminator

Matches values based on a specified discriminant field.

Details

This function is used to define pattern matching on objects that follow a discriminated union structure, where a specific field (e.g., type, kind, _tag) determines the variant of the object. It allows matching multiple values of the discriminant and provides a function to handle the matched cases.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ type: "A"; a: string } | { type: "B"; b: number } | { type: "C"; c: boolean }>(),
  Match.discriminator("type")("A", "B", (_) => `A or B: ${_.type}`),
  Match.discriminator("type")("C", (_) => `C(${_.c})`),
  Match.exhaustive
)

Signature

declare const discriminator: <D extends string>(
  field: D
) => <R, P extends Types.Tags<D, R> & string, Ret, Fn extends (_: Extract<R, Record<D, P>>) => Ret>(
  ...pattern: [first: P, ...values: Array<P>, f: Fn]
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<D, P>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<D, P>>>>,
  A | ReturnType<Fn>,
  Pr,
  Ret
>

Source

Since v1.0.0

discriminatorStartsWith

Matches values where a specified field starts with a given prefix.

Details

This function is useful for working with discriminated unions where the discriminant field follows a hierarchical or namespaced structure. It allows you to match values based on whether the specified field starts with a given prefix, making it easier to handle grouped cases.

Instead of checking for exact matches, this function lets you match values that share a common prefix. For example, if your discriminant field contains hierarchical names like "A", "A.A", and "B", you can match all values starting with "A" using a single rule.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ type: "A" } | { type: "B" } | { type: "A.A" } | {}>(),
  Match.discriminatorStartsWith("type")("A", (_) => 1 as const),
  Match.discriminatorStartsWith("type")("B", (_) => 2 as const),
  Match.orElse((_) => 3 as const)
)

console.log(match({ type: "A" })) // 1
console.log(match({ type: "B" })) // 2
console.log(match({ type: "A.A" })) // 1

Signature

declare const discriminatorStartsWith: <D extends string>(
  field: D
) => <R, P extends string, Ret, Fn extends (_: Extract<R, Record<D, `${P}${string}`>>) => Ret>(
  pattern: P,
  f: Fn
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<D, `${P}${string}`>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<D, `${P}${string}`>>>>,
  A | ReturnType<Fn>,
  Pr,
  Ret
>

Source

Since v1.0.0

discriminators

Matches values based on a field that serves as a discriminator, mapping each possible value to a corresponding handler.

Details

This function simplifies working with discriminated unions by letting you define a set of handlers for each possible value of a given field. Instead of chaining multiple calls to discriminator, this function allows defining all possible cases at once using an object where the keys are the possible values of the field, and the values are the corresponding handler functions.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ type: "A"; a: string } | { type: "B"; b: number } | { type: "C"; c: boolean }>(),
  Match.discriminators("type")({
    A: (a) => a.a,
    B: (b) => b.b,
    C: (c) => c.c
  }),
  Match.exhaustive
)

Signature

declare const discriminators: <D extends string>(
  field: D
) => <
  R,
  Ret,
  P extends { readonly [Tag in Types.Tags<D, R> & string]?: ((_: Extract<R, Record<D, Tag>>) => Ret) | undefined } & {
    readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never
  }
>(
  fields: P
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<D, keyof P>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<D, keyof P>>>>,
  A | ReturnType<P[keyof P] & {}>,
  Pr,
  Ret
>

Source

Since v1.0.0

discriminatorsExhaustive

Matches values based on a discriminator field and ensures all cases are handled.

*Details+

This function is similar to discriminators, but requires that all possible cases are explicitly handled. It is useful when working with discriminated unions, where a specific field (e.g., "type") determines the shape of an object. Each possible value of the field must have a corresponding handler, ensuring exhaustiveness checking at compile time.

This function does not require Match.exhaustive at the end of the pipeline because it enforces exhaustiveness by design.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ type: "A"; a: string } | { type: "B"; b: number } | { type: "C"; c: boolean }>(),
  Match.discriminatorsExhaustive("type")({
    A: (a) => a.a,
    B: (b) => b.b,
    C: (c) => c.c
  })
)

Signature

declare const discriminatorsExhaustive: <D extends string>(
  field: D
) => <
  R,
  Ret,
  P extends { readonly [Tag in Types.Tags<D, R> & string]: (_: Extract<R, Record<D, Tag>>) => Ret } & {
    readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never
  }
>(
  fields: P
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => [Pr] extends [never] ? (u: I) => Unify<A | ReturnType<P[keyof P]>> : Unify<A | ReturnType<P[keyof P]>>

Source

Since v1.0.0

not

Excludes a specific value from matching while allowing all others.

Details

This function is useful when you need to handle all values except one or more specific cases. Instead of listing all possible matches manually, this function simplifies the logic by allowing you to specify values to exclude. Any excluded value will bypass the provided function and continue matching through other cases.

Example (Ignoring a Specific Value)

import { Match } from "effect"

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match any value except "hi", returning "ok"
  Match.not("hi", () => "ok"),
  // Fallback case for when the value is "hi"
  Match.orElse(() => "fallback")
)

console.log(match("hello"))
// Output: "ok"

console.log(match("hi"))
// Output: "fallback"

Signature

declare const not: <
  R,
  const P extends Types.PatternPrimitive<R> | Types.PatternBase<R>,
  Ret,
  Fn extends (_: Exclude<R, Types.ExtractMatch<R, Types.PForExclude<P>>>) => Ret
>(
  pattern: P,
  f: Fn
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddOnly<F, Types.WhenMatch<R, P>>,
  Types.ApplyFilters<I, Types.AddOnly<F, Types.WhenMatch<R, P>>>,
  A | ReturnType<Fn>,
  Pr,
  Ret
>

Source

Since v1.0.0

tag

The Match.tag function allows pattern matching based on the _tag field in a Discriminated Union. You can specify multiple tags to match within a single pattern.

Note

The Match.tag function relies on the convention within the Effect ecosystem of naming the tag field as "_tag". Ensure that your discriminated unions follow this naming convention for proper functionality.

Example (Matching a Discriminated Union by Tag)

import { Match } from "effect"

type Event =
  | { readonly _tag: "fetch" }
  | { readonly _tag: "success"; readonly data: string }
  | { readonly _tag: "error"; readonly error: Error }
  | { readonly _tag: "cancel" }

// Create a Matcher for Either<number, string>
const match = Match.type<Event>().pipe(
  // Match either "fetch" or "success"
  Match.tag("fetch", "success", () => `Ok!`),
  // Match "error" and extract the error message
  Match.tag("error", (event) => `Error: ${event.error.message}`),
  // Match "cancel"
  Match.tag("cancel", () => "Cancelled"),
  Match.exhaustive
)

console.log(match({ _tag: "success", data: "Hello" }))
// Output: "Ok!"

console.log(match({ _tag: "error", error: new Error("Oops!") }))
// Output: "Error: Oops!"

Signature

declare const tag: <
  R,
  P extends Types.Tags<"_tag", R> & string,
  Ret,
  Fn extends (_: Extract<R, Record<"_tag", P>>) => Ret
>(
  ...pattern: [first: P, ...values: Array<P>, f: Fn]
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<"_tag", P>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<"_tag", P>>>>,
  ReturnType<Fn> | A,
  Pr,
  Ret
>

Source

Since v1.0.0

tagStartsWith

Matches values where the _tag field starts with a given prefix.

Details

This function allows you to match on values in a discriminated union based on whether the _tag field starts with a specified prefix. It is useful for handling hierarchical or namespaced tags, where multiple related cases share a common prefix.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ _tag: "A" } | { _tag: "B" } | { _tag: "A.A" } | {}>(),
  Match.tagStartsWith("A", (_) => 1 as const),
  Match.tagStartsWith("B", (_) => 2 as const),
  Match.orElse((_) => 3 as const)
)

console.log(match({ _tag: "A" })) // 1
console.log(match({ _tag: "B" })) // 2
console.log(match({ _tag: "A.A" })) // 1

Signature

declare const tagStartsWith: <
  R,
  P extends string,
  Ret,
  Fn extends (_: Extract<R, Record<"_tag", `${P}${string}`>>) => Ret
>(
  pattern: P,
  f: Fn
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<"_tag", `${P}${string}`>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<"_tag", `${P}${string}`>>>>,
  ReturnType<Fn> | A,
  Pr,
  Ret
>

Source

Since v1.0.0

tags

Matches values based on their _tag field, mapping each tag to a corresponding handler.

Details

This function provides a way to handle discriminated unions by mapping _tag values to specific functions. Each handler receives the matched value and returns a transformed result. If all possible tags are handled, you can enforce exhaustiveness using Match.exhaustive to ensure no case is missed.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ _tag: "A"; a: string } | { _tag: "B"; b: number } | { _tag: "C"; c: boolean }>(),
  Match.tags({
    A: (a) => a.a,
    B: (b) => b.b,
    C: (c) => c.c
  }),
  Match.exhaustive
)

Signature

declare const tags: <
  R,
  Ret,
  P extends {
    readonly [Tag in Types.Tags<"_tag", R> & string]?: ((_: Extract<R, Record<"_tag", Tag>>) => Ret) | undefined
  } & { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never }
>(
  fields: P
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Extract<R, Record<"_tag", keyof P>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<"_tag", keyof P>>>>,
  A | ReturnType<P[keyof P] & {}>,
  Pr,
  Ret
>

Source

Since v1.0.0

tagsExhaustive

Matches values based on their _tag field and requires handling of all possible cases.

Details

This function is designed for discriminated unions where every possible _tag value must have a corresponding handler. Unlike tags, this function ensures exhaustiveness, meaning all cases must be explicitly handled. If a _tag value is missing from the mapping, TypeScript will report an error.

Example

import { Match, pipe } from "effect"

const match = pipe(
  Match.type<{ _tag: "A"; a: string } | { _tag: "B"; b: number } | { _tag: "C"; c: boolean }>(),
  Match.tagsExhaustive({
    A: (a) => a.a,
    B: (b) => b.b,
    C: (c) => c.c
  })
)

Signature

declare const tagsExhaustive: <
  R,
  Ret,
  P extends { readonly [Tag in Types.Tags<"_tag", R> & string]: (_: Extract<R, Record<"_tag", Tag>>) => Ret } & {
    readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never
  }
>(
  fields: P
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => [Pr] extends [never] ? (u: I) => Unify<A | ReturnType<P[keyof P]>> : Unify<A | ReturnType<P[keyof P]>>

Source

Since v1.0.0

when

Defines a condition for matching values.

Details

This function enables pattern matching by checking whether a given value satisfies a condition. It supports both direct value comparisons and predicate functions. If the condition is met, the associated function is executed.

This function is useful when defining matchers that need to check for specific values or apply logical conditions to determine a match. It works well with structured objects and primitive types.

Example (Matching with Values and Predicates)

import { Match } from "effect"

// Create a matcher for objects with an "age" property
const match = Match.type<{ age: number }>().pipe(
  // Match when age is greater than 18
  Match.when({ age: (age) => age > 18 }, (user) => `Age: ${user.age}`),
  // Match when age is exactly 18
  Match.when({ age: 18 }, () => "You can vote"),
  // Fallback case for all other ages
  Match.orElse((user) => `${user.age} is too young`)
)

console.log(match({ age: 20 }))
// Output: "Age: 20"

console.log(match({ age: 18 }))
// Output: "You can vote"

console.log(match({ age: 4 }))
// Output: "4 is too young"

See

  • whenOr Use this when multiple patterns should match in a single condition.
  • whenAnd Use this when a value must match all provided patterns.
  • orElse Provides a fallback when no patterns match.

Signature

declare const when: <
  R,
  const P extends Types.PatternPrimitive<R> | Types.PatternBase<R>,
  Ret,
  Fn extends (_: Types.WhenMatch<R, P>) => Ret
>(
  pattern: P,
  f: Fn
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Types.PForExclude<P>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Types.PForExclude<P>>>,
  A | ReturnType<Fn>,
  Pr,
  Ret
>

Source

Since v1.0.0

whenAnd

Matches a value that satisfies all provided patterns.

Details

This function allows defining a condition where a value must match all the given patterns simultaneously. If the value satisfies every pattern, the associated function is executed.

Unlike when, which matches a single pattern at a time, this function ensures that multiple conditions are met before executing the callback. It is useful when checking for values that need to fulfill multiple criteria at once.

Example

import { Match } from "effect"

type User = { readonly age: number; readonly role: "admin" | "user" }

const checkUser = Match.type<User>().pipe(
  Match.whenAnd({ age: (n) => n >= 18 }, { role: "admin" }, () => "Admin access granted"),
  Match.orElse(() => "Access denied")
)

console.log(checkUser({ age: 20, role: "admin" }))
// Output: "Admin access granted"

console.log(checkUser({ age: 20, role: "user" }))
// Output: "Access denied"

Signature

declare const whenAnd: <
  R,
  const P extends ReadonlyArray<Types.PatternPrimitive<R> | Types.PatternBase<R>>,
  Ret,
  Fn extends (_: Types.WhenMatch<R, T.UnionToIntersection<P[number]>>) => Ret
>(
  ...args: [...patterns: P, f: Fn]
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Types.PForExclude<T.UnionToIntersection<P[number]>>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Types.PForExclude<T.UnionToIntersection<P[number]>>>>,
  A | ReturnType<Fn>,
  Pr
>

Source

Since v1.0.0

whenOr

Matches one of multiple patterns in a single condition.

Details

This function allows defining a condition where a value matches any of the provided patterns. If a match is found, the associated function is executed. It simplifies cases where multiple patterns share the same handling logic.

Unlike when, which requires separate conditions for each pattern, this function enables combining them into a single statement, making the matcher more concise.

Example

import { Match } from "effect"

type ErrorType =
  | { readonly _tag: "NetworkError"; readonly message: string }
  | { readonly _tag: "TimeoutError"; readonly duration: number }
  | { readonly _tag: "ValidationError"; readonly field: string }

const handleError = Match.type<ErrorType>().pipe(
  Match.whenOr({ _tag: "NetworkError" }, { _tag: "TimeoutError" }, () => "Retry the request"),
  Match.when({ _tag: "ValidationError" }, (_) => `Invalid field: ${_.field}`),
  Match.exhaustive
)

console.log(handleError({ _tag: "NetworkError", message: "No connection" }))
// Output: "Retry the request"

console.log(handleError({ _tag: "ValidationError", field: "email" }))
// Output: "Invalid field: email"

Signature

declare const whenOr: <
  R,
  const P extends ReadonlyArray<Types.PatternPrimitive<R> | Types.PatternBase<R>>,
  Ret,
  Fn extends (_: Types.WhenMatch<R, P[number]>) => Ret
>(
  ...args: [...patterns: P, f: Fn]
) => <I, F, A, Pr>(
  self: Matcher<I, F, R, A, Pr, Ret>
) => Matcher<
  I,
  Types.AddWithout<F, Types.PForExclude<P[number]>>,
  Types.ApplyFilters<I, Types.AddWithout<F, Types.PForExclude<P[number]>>>,
  A | ReturnType<Fn>,
  Pr,
  Ret
>

Source

Since v1.0.0

Model

Case (type alias)

Signature

type Case = When | Not

Source

Since v1.0.0

Matcher (type alias)

Pattern matching follows a structured process:

  • Creating a matcher: Define a Matcher that operates on either a specific Match.type or Match.value.

  • Defining patterns: Use combinators such as Match.when, Match.not, and Match.tag to specify matching conditions.

  • Completing the match: Apply a finalizer such as Match.exhaustive, Match.orElse, or Match.option to determine how unmatched cases should be handled.

Example

import { Match } from "effect"

// Simulated dynamic input that can be a string or a number
const input: string | number = "some input"

//      ┌─── string
//      ▼
const result = Match.value(input).pipe(
  // Match if the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Match if the value is a string
  Match.when(Match.string, (s) => `string: ${s}`),
  // Ensure all possible cases are covered
  Match.exhaustive
)

console.log(result)
// Output: "string: some input"

Signature

type Matcher<Input, Filters, RemainingApplied, Result, Provided, Return> =
  | TypeMatcher<Input, Filters, RemainingApplied, Result, Return>
  | ValueMatcher<Input, Filters, RemainingApplied, Result, Provided, Return>

Source

Since v1.0.0

Not (interface)

Signature

export interface Not {
  readonly _tag: "Not"
  guard(u: unknown): boolean
  evaluate(input: unknown): any
}

Source

Since v1.0.0

SafeRefinement (interface)

Signature

export interface SafeRefinement<in A, out R = A> {
  readonly [SafeRefinementId]: (a: A) => R
}

Source

Since v1.0.0

TypeMatcher (interface)

Signature

export interface TypeMatcher<in Input, out Filters, out Remaining, out Result, out Return = any> extends Pipeable {
  readonly _tag: "TypeMatcher"
  readonly [MatcherTypeId]: {
    readonly _input: T.Contravariant<Input>
    readonly _filters: T.Covariant<Filters>
    readonly _remaining: T.Covariant<Remaining>
    readonly _result: T.Covariant<Result>
    readonly _return: T.Covariant<Return>
  }
  readonly cases: ReadonlyArray<Case>
  add<I, R, RA, A>(_case: Case): TypeMatcher<I, R, RA, A>
}

Source

Since v1.0.0

ValueMatcher (interface)

Signature

export interface ValueMatcher<in Input, Filters, out Remaining, out Result, Provided, out Return = any>
  extends Pipeable {
  readonly _tag: "ValueMatcher"
  readonly [MatcherTypeId]: {
    readonly _input: T.Contravariant<Input>
    readonly _filters: T.Covariant<Filters>
    readonly _result: T.Covariant<Result>
    readonly _return: T.Covariant<Return>
  }
  readonly provided: Provided
  readonly value: Either.Either<Provided, Remaining>
  add<I, R, RA, A, Pr>(_case: Case): ValueMatcher<I, R, RA, A, Pr>
}

Source

Since v1.0.0

When (interface)

Signature

export interface When {
  readonly _tag: "When"
  guard(u: unknown): boolean
  evaluate(input: unknown): any
}

Source

Since v1.0.0

Predicates

any

Matches any value without restrictions.

Signature

declare const any: SafeRefinement<unknown, any>

Source

Since v1.0.0

bigint

Matches values of type bigint.

Signature

declare const bigint: Predicate.Refinement<unknown, bigint>

Source

Since v1.0.0

boolean

Matches values of type boolean.

Signature

declare const boolean: Predicate.Refinement<unknown, boolean>

Source

Since v1.0.0

date

Matches values that are instances of Date.

Signature

declare const date: Predicate.Refinement<unknown, Date>

Source

Since v1.0.0

defined

Matches any defined (non-null and non-undefined) value.

Signature

declare const defined: <A>(u: A) => u is A & {}

Source

Since v1.0.0

instanceOf

Matches instances of a given class.

Signature

declare const instanceOf: <A extends abstract new (...args: any) => any>(
  constructor: A
) => SafeRefinement<InstanceType<A>, never>

Source

Since v1.0.0

instanceOfUnsafe

Signature

declare const instanceOfUnsafe: <A extends abstract new (...args: any) => any>(
  constructor: A
) => SafeRefinement<InstanceType<A>, InstanceType<A>>

Source

Since v1.0.0

is

Matches a specific set of literal values (e.g., Match.is("a", 42, true)).

Signature

declare const is: <Literals extends ReadonlyArray<string | number | bigint | boolean | null>>(
  ...literals: Literals
) => Predicate.Refinement<unknown, Literals[number]>

Source

Since v1.0.0

nonEmptyString

Matches non-empty strings.

Signature

declare const nonEmptyString: SafeRefinement<string, never>

Source

Since v1.0.0

null

Matches the value null.

Signature

declare const null: Predicate.Refinement<unknown, null>

Source

Since v1.0.0

number

Matches values of type number.

Signature

declare const number: Predicate.Refinement<unknown, number>

Source

Since v1.0.0

record

Matches objects where keys are string or symbol and values are unknown.

Signature

declare const record: Predicate.Refinement<unknown, { [x: string]: unknown; [x: symbol]: unknown }>

Source

Since v1.0.0

string

Matches values of type string.

Signature

declare const string: Predicate.Refinement<unknown, string>

Source

Since v1.0.0

symbol

Matches values of type symbol.

Signature

declare const symbol: Predicate.Refinement<unknown, symbol>

Source

Since v1.0.0

undefined

Matches the value undefined.

Signature

declare const undefined: Predicate.Refinement<unknown, undefined>

Source

Since v1.0.0

Symbols

MatcherTypeId

Signature

declare const MatcherTypeId: unique symbol

Source

Since v1.0.0

MatcherTypeId (type alias)

Signature

type MatcherTypeId = typeof MatcherTypeId

Source

Since v1.0.0

SafeRefinementId

Signature

declare const SafeRefinementId: unique symbol

Source

Since v1.0.0

SafeRefinementId (type alias)

Signature

type SafeRefinementId = typeof SafeRefinementId

Source

Since v1.0.0

utils

Types (namespace)

Source

Since v1.0.0

Without (interface)

Signature

export interface Without<out X> {
  readonly _tag: "Without"
  readonly _X: X
}

Source

Since v1.0.0

Only (interface)

Signature

export interface Only<out X> {
  readonly _tag: "Only"
  readonly _X: X
}

Source

Since v1.0.0

WhenMatch (type alias)

Signature

type WhenMatch<R, P> = [0] extends [1 & R]
  ? PForMatch<P>
  : P extends SafeRefinement<infer SP, never>
    ? SP
    : P extends Predicate.Refinement<infer _R, infer RP>
      ? // try to narrow refinement
        [Extract<R, RP>] extends [infer X]
        ? [X] extends [never]
          ? // fallback to original refinement
            RP
          : X
        : never
      : P extends PredicateA<infer PP>
        ? PP
        : ExtractMatch<R, PForMatch<P>>

Source

Since v1.0.0

NotMatch (type alias)

Signature

type NotMatch<R, P> = Exclude<R, ExtractMatch<R, PForExclude<P>>>

Source

Since v1.0.0

PForMatch (type alias)

Signature

type PForMatch<P> = [SafeRefinementP<ResolvePred<P>>] extends [infer X] ? X : never

Source

Since v1.0.0

PForExclude (type alias)

Signature

type PForExclude<P> = [SafeRefinementR<ToSafeRefinement<P>>] extends [infer X] ? X : never

Source

Since v1.0.0

PatternBase (type alias)

Signature

type PatternBase<A> =
  A extends ReadonlyArray<infer _T>
    ? ReadonlyArray<any> | PatternPrimitive<A>
    : A extends Record<string, any>
      ? Partial<{ [K in keyof A]: PatternPrimitive<A[K] & {}> | PatternBase<A[K] & {}> }>
      : never

Source

Since v1.0.0

PatternPrimitive (type alias)

Signature

type PatternPrimitive<A> = PredicateA<A> | A | SafeRefinement<any>

Source

Since v1.0.0

AddWithout (type alias)

Signature

type AddWithout<A, X> = [A] extends [Without<infer WX>]
  ? Without<X | WX>
  : [A] extends [Only<infer OX>]
    ? Only<Exclude<OX, X>>
    : never

Source

Since v1.0.0

AddOnly (type alias)

Signature

type AddOnly<A, X> = [A] extends [Without<infer WX>]
  ? [X] extends [WX]
    ? never
    : Only<X>
  : [A] extends [Only<infer OX>]
    ? [X] extends [OX]
      ? Only<X>
      : never
    : never

Source

Since v1.0.0

ApplyFilters (type alias)

Signature

type ApplyFilters<I, A> = A extends Only<infer X> ? X : A extends Without<infer X> ? Exclude<I, X> : never

Source

Since v1.0.0

Tags (type alias)

Signature

type Tags<D, P> = P extends Record<D, infer X> ? X : never

Source

Since v1.0.0

ArrayToIntersection (type alias)

Signature

type ArrayToIntersection<A> = T.UnionToIntersection<A[number]>

Source

Since v1.0.0

ExtractMatch (type alias)

Signature

type ExtractMatch<I, P> = [ExtractAndNarrow<I, P>] extends [infer EI] ? EI : never

Source

Since v1.0.0

withReturnType

Ensures that all branches of a matcher return a specific type.

Details

This function enforces a consistent return type across all pattern-matching branches. By specifying a return type, TypeScript will check that every matching condition produces a value of the expected type.

Important: This function must be the first step in the matcher pipeline. If used later, TypeScript will not enforce type consistency correctly.

Example (Validating Return Type Consistency)

import { Match } from "effect"

const match = Match.type<{ a: number } | { b: string }>().pipe(
  // Ensure all branches return a string
  Match.withReturnType<string>(),
  // ❌ Type error: 'number' is not assignable to type 'string'
  // @ts-expect-error
  Match.when({ a: Match.number }, (_) => _.a),
  // ✅ Correct: returns a string
  Match.when({ b: Match.string }, (_) => _.b),
  Match.exhaustive
)

Signature

declare const withReturnType: <Ret>() => <I, F, R, A, Pr, _>(
  self: Matcher<I, F, R, A, Pr, _>
) => [Ret] extends [[A] extends [never] ? any : A]
  ? Matcher<I, F, R, A, Pr, Ret>
  : "withReturnType constraint does not extend Result type"

Source

Since v1.0.0