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 specificMatch.type
orMatch.value
. -
Defining patterns: Use combinators such as
Match.when
,Match.not
, andMatch.tag
to specify matching conditions. -
Completing the match: Apply a finalizer such as
Match.exhaustive
,Match.orElse
, orMatch.option
to determine how unmatched cases should be handled.
Since v1.0.0
Exports Grouped by Category
- Completion
- Creating a matcher
- Defining patterns
- Model
- Predicates
- Symbols
- utils
- Types (namespace)
- Without (interface)
- Only (interface)
- WhenMatch (type alias)
- NotMatch (type alias)
- PForMatch (type alias)
- PForExclude (type alias)
- PatternBase (type alias)
- PatternPrimitive (type alias)
- AddWithout (type alias)
- AddOnly (type alias)
- ApplyFilters (type alias)
- Tags (type alias)
- ArrayToIntersection (type alias)
- ExtractMatch (type alias)
- withReturnType
- Types (namespace)
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>
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>
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>>
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>
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>
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>
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]>>
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>
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]>>
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
>
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
>
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
>
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]>>
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
>
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
>
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
>
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
>
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]>>
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
>
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
>
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
>
Since v1.0.0
Model
Case (type alias)
Signature
type Case = When | Not
Since v1.0.0
Matcher (type alias)
Pattern matching follows a structured process:
-
Creating a matcher: Define a
Matcher
that operates on either a specificMatch.type
orMatch.value
. -
Defining patterns: Use combinators such as
Match.when
,Match.not
, andMatch.tag
to specify matching conditions. -
Completing the match: Apply a finalizer such as
Match.exhaustive
,Match.orElse
, orMatch.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>
Since v1.0.0
Not (interface)
Signature
export interface Not {
readonly _tag: "Not"
guard(u: unknown): boolean
evaluate(input: unknown): any
}
Since v1.0.0
SafeRefinement (interface)
Signature
export interface SafeRefinement<in A, out R = A> {
readonly [SafeRefinementId]: (a: A) => R
}
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>
}
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>
}
Since v1.0.0
When (interface)
Signature
export interface When {
readonly _tag: "When"
guard(u: unknown): boolean
evaluate(input: unknown): any
}
Since v1.0.0
Predicates
any
Matches any value without restrictions.
Signature
declare const any: SafeRefinement<unknown, any>
Since v1.0.0
bigint
Matches values of type bigint
.
Signature
declare const bigint: Predicate.Refinement<unknown, bigint>
Since v1.0.0
boolean
Matches values of type boolean
.
Signature
declare const boolean: Predicate.Refinement<unknown, boolean>
Since v1.0.0
date
Matches values that are instances of Date
.
Signature
declare const date: Predicate.Refinement<unknown, Date>
Since v1.0.0
defined
Matches any defined (non-null and non-undefined) value.
Signature
declare const defined: <A>(u: A) => u is A & {}
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>
Since v1.0.0
instanceOfUnsafe
Signature
declare const instanceOfUnsafe: <A extends abstract new (...args: any) => any>(
constructor: A
) => SafeRefinement<InstanceType<A>, InstanceType<A>>
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]>
Since v1.0.0
nonEmptyString
Matches non-empty strings.
Signature
declare const nonEmptyString: SafeRefinement<string, never>
Since v1.0.0
null
Matches the value null
.
Signature
declare const null: Predicate.Refinement<unknown, null>
Since v1.0.0
number
Matches values of type number
.
Signature
declare const number: Predicate.Refinement<unknown, number>
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 }>
Since v1.0.0
string
Matches values of type string
.
Signature
declare const string: Predicate.Refinement<unknown, string>
Since v1.0.0
symbol
Matches values of type symbol
.
Signature
declare const symbol: Predicate.Refinement<unknown, symbol>
Since v1.0.0
undefined
Matches the value undefined
.
Signature
declare const undefined: Predicate.Refinement<unknown, undefined>
Since v1.0.0
Symbols
MatcherTypeId
Signature
declare const MatcherTypeId: unique symbol
Since v1.0.0
MatcherTypeId (type alias)
Signature
type MatcherTypeId = typeof MatcherTypeId
Since v1.0.0
SafeRefinementId
Signature
declare const SafeRefinementId: unique symbol
Since v1.0.0
SafeRefinementId (type alias)
Signature
type SafeRefinementId = typeof SafeRefinementId
Since v1.0.0
utils
Types (namespace)
Since v1.0.0
Without (interface)
Signature
export interface Without<out X> {
readonly _tag: "Without"
readonly _X: X
}
Since v1.0.0
Only (interface)
Signature
export interface Only<out X> {
readonly _tag: "Only"
readonly _X: X
}
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>>
Since v1.0.0
NotMatch (type alias)
Signature
type NotMatch<R, P> = Exclude<R, ExtractMatch<R, PForExclude<P>>>
Since v1.0.0
PForMatch (type alias)
Signature
type PForMatch<P> = [SafeRefinementP<ResolvePred<P>>] extends [infer X] ? X : never
Since v1.0.0
PForExclude (type alias)
Signature
type PForExclude<P> = [SafeRefinementR<ToSafeRefinement<P>>] extends [infer X] ? X : never
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
Since v1.0.0
PatternPrimitive (type alias)
Signature
type PatternPrimitive<A> = PredicateA<A> | A | SafeRefinement<any>
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
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
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
Since v1.0.0
Tags (type alias)
Signature
type Tags<D, P> = P extends Record<D, infer X> ? X : never
Since v1.0.0
ArrayToIntersection (type alias)
Signature
type ArrayToIntersection<A> = T.UnionToIntersection<A[number]>
Since v1.0.0
ExtractMatch (type alias)
Signature
type ExtractMatch<I, P> = [ExtractAndNarrow<I, P>] extends [infer EI] ? EI : never
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"
Since v1.0.0