Match overview
Compilation-free pattern matcher that branches on predicates and refinements.
Mental model
-
Type<Input, Output, Rest>is a matcher in progress.Inputis the original value being matched.Outputis the union of result types contributed so far.Restis the part ofInputthat no previous refinement has eliminated.
- Each
when/whenIs/whenOr/whenAnd/tryFunctionshort-circuits on success: as soon as a case produces an output, every subsequent case is skipped. - Terminate with {@link exhaustive} (compile-time check that
Restisnever) or {@link orElse} (runtime fallback).
Common tasks
- Create: {@link make}
- Add cases: {@link when}, {@link whenIs}, {@link whenIsOr}, {@link tryFunction}
- Combine cases: {@link whenOr}, {@link whenAnd}
- Terminate: {@link exhaustive}, {@link orElse}, {@link unsafeWhen}
Quickstart
Example (Sign of a number)
import { Number, pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
const sign = (n: number) =>
pipe(
n,
MMatch.make,
MMatch.when(Number.isLessThan(0), () => "negative"),
MMatch.when(Number.isGreaterThan(0), () => "positive"),
MMatch.orElse(() => "zero")
)
console.log(sign(5)) // 'positive'
console.log(sign(0)) // 'zero'
Table of contents
Constructors
make
Builds a new matcher.
- Use to start building a matcher for a value.
- The returned matcher has no cases yet.
- Chain with
when,whenIs, etc. to add cases.
Example (Create matcher)
import * as MMatch from "@parischap/effect-lib/MMatch"
const m = MMatch.make(5)
console.log(m.input) // 5
Signature
export declare const make: <Input>(input: Input) => Type<Input, never, Input>
Models
Type (class)
Type that represents a matcher
Signature
export declare class Type<Input, Output, Rest> {
private constructor({ input, output }: MTypes.Data<Type<Input, Output, Rest>>)
}
make (static method)
Static constructor
Signature
static make<Input, Output, Rest extends Input>(
params: MTypes.Data<Type<Input, Output, Rest>>,
): Type<Input, Output, Rest>
[MData.idSymbol] (method)
Returns the id of this
Signature
[MData.idSymbol](): string | (() => string)
input (property)
The input to match
Signature
readonly input: Input
output (property)
The output of the matcher when it has been found
Signature
readonly output: Option.Option<Output>
Module markers
moduleTag
Module tag
Signature
export declare const moduleTag: "@parischap/effect-lib/Match/"
Utils
exhaustive
Returns the output of the matcher if all cases are covered. Shows a type error if Rest is not never, meaning there are still uncovered input cases.
- Use only when you have covered all possible input cases.
- Throws if no case matched.
- TypeScript ensures exhaustiveness through the
Resttype parameter. - Prefer
orElsefor non-exhaustive patterns.
Example (Exhaustive match)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
import * as Number from "effect/Number"
enum Status {
Active = "a",
Inactive = "i"
}
const describe = (s: Status) =>
pipe(
s,
MMatch.make,
MMatch.whenIs(Status.Active, () => "Active"),
MMatch.whenIs(Status.Inactive, () => "Inactive"),
MMatch.exhaustive
)
console.log(describe(Status.Active)) // "Active"
Signature
export declare const exhaustive: <Input, Output>(self: Type<Input, Output, never>) => Output
orElse
Provides a default value if no match has occurred. Returns the output if there is one, otherwise returns f(self.input).
- Use to handle the case where no previous predicates matched.
- Receives the original input value.
- Guaranteed to produce a result (exhaustive).
Example (Provide default value)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
import * as Number from "effect/Number"
const result = pipe(
5,
MMatch.make,
MMatch.when(Number.isLessThan(0), () => "negative"),
MMatch.orElse(() => "non-negative")
)
console.log(result) // "non-negative"
Signature
export declare const orElse: <Input, Rest extends Input, Output1>(
f: (value: NoInfer<Rest>) => Output1
) => <Output>(self: Type<Input, Output, Rest>) => Output | Output1
tryFunction
Tries applying a function that returns an Option. Matches if the function returns a some. Returns a copy of self if self already has an output. Otherwise, applies f to self.input. If f returns some, sets the output. If f returns none, tries the next case.
- Use when matching using optional computations.
- Useful for extracting values from containers like arrays.
- The match succeeds only when
freturnsOption.some.
Example (Match with optional function)
import { pipe } from "effect"
import { Array } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
const result = pipe(
[1, 2, 3],
MMatch.make,
MMatch.tryFunction(Array.get(1)),
MMatch.orElse(() => 0)
)
console.log(result) // 2
Signature
export declare const tryFunction: <Input, Rest extends Input, Output1>(
f: (value: NoInfer<Rest>) => Option.Option<Output1>
) => <Output>(self: Type<Input, Output, Rest>) => Type<Input, Output | Output1, Rest>
unsafeWhen
Terminates the matcher with f applied to the input, without evaluating the runtime predicate. The supplied refinement is only used at the type level to narrow the input handed to f.
- Use to remove a final type-level case the matcher can’t otherwise prove (e.g. the last branch of a discriminated union after every other variant has been matched).
- When a previous case already produced an output, that output is returned and
fis not called. - Unsafe: if the runtime value does not actually satisfy
refinement,fis still invoked, silently producing a value typed as if narrowing succeeded.
Example (Closing a discriminated union)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
type Shape = { readonly _tag: "Circle"; readonly r: number } | { readonly _tag: "Square"; readonly s: number }
const area = (shape: Shape) =>
pipe(
shape,
MMatch.make,
MMatch.when(
(s): s is Extract<Shape, { readonly _tag: "Circle" }> => s._tag === "Circle",
(c) => Math.PI * c.r * c.r
),
MMatch.unsafeWhen(
(s): s is Extract<Shape, { readonly _tag: "Square" }> => s._tag === "Square",
(s) => s.s * s.s
)
)
console.log(area({ _tag: "Circle", r: 1 })) // 3.141592653589793
console.log(area({ _tag: "Square", s: 2 })) // 4
Signature
export declare const unsafeWhen: <Input, Rest extends Input, Refined extends Rest, Output1>(
_predicate: Predicate.Refinement<Rest, Refined>,
f: (value: NoInfer<Refined>) => Output1
) => <Output>(self: Type<Input, Output, Rest>) => Output | Output1
when
Matches against a refinement or a predicate. Returns a copy of self if self already has an output. Otherwise, applies the predicate/refinement to self.input. Returns a copy of self if the predicate returns false. Otherwise, returns a copy of self with the output set to f(self.input).
- Use to add a case to the matcher.
- If a previous case matched, this case is skipped.
- Use with refinements for type narrowing.
- Use with predicates for boolean checks.
Example (Add matching case)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
import * as Number from "effect/Number"
const result = pipe(
5,
MMatch.make,
MMatch.when(Number.isLessThan(0), () => "negative"),
MMatch.when(Number.isGreaterThan(0), () => "positive"),
MMatch.orElse(() => "zero")
)
console.log(result) // "positive"
Signature
export declare const when: {
<Input, Rest extends Input, Refined extends Rest, Output1>(
predicate: Predicate.Refinement<NoInfer<Rest>, Refined>,
f: (value: Refined) => Output1
): <Output>(self: Type<Input, Output, Rest>) => Type<Input, Output | Output1, Exclude<Rest, Refined>>
<Input, Rest extends Input, Output1>(
predicate: Predicate.Predicate<NoInfer<Rest>>,
f: (value: Rest) => Output1
): <Output>(self: Type<Input, Output, Rest>) => Type<Input, Output | Output1, Rest>
}
whenAnd
Matches using multiple predicates where all must be satisfied. The match occurs if all predicates return true. Returns a copy of self if self already has an output. Otherwise, applies all predicates to self.input and returns a copy with the output set to f(self.input) if all match.
- Use when multiple conditions must all be true to trigger the same handler.
- Efficient alternative to chaining multiple
whencalls with AND logic. - All predicates are evaluated.
Example (Match with all predicates required)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
import * as Number from "effect/Number"
const result = pipe(
5,
MMatch.make,
MMatch.whenAnd(Number.isGreaterThan(0), Number.isLessThan(10), () => "in range"),
MMatch.orElse(() => "out of range")
)
console.log(result) // "in range"
Signature
export declare const whenAnd: <
Input,
Rest extends Input,
R extends MTypes.ReadonlyOverTwo<Predicate.Predicate<Rest>>,
Output1
>(
...args: readonly [
...refinements: R,
f: (
value: NoInfer<MTypes.IntersectAndSimplify<MTypes.ToKeyIntersection<MPredicate.PredicatesToTargets<R>>, Rest>>
) => Output1
]
) => <Output>(
self: Type<Input, Output, Rest>
) => Type<Input, Output | Output1, Exclude<Rest, MTypes.ToKeyIntersection<MPredicate.PredicatesToCoverages<R>>>>
whenIs
Matches against a primitive value using strict equality. Returns a copy of self if self already has an output. Otherwise, self.input is compared to the provided value using strict equality. Returns a copy of self if the two values are not equal. Otherwise, returns a copy of self with the output set to the result of f(self.input).
- Use when matching against specific literal values.
- Comparison uses strict equality (
===). - Useful for enum matching or constant comparisons.
Example (Match against value)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
enum Status {
A = "a",
B = "b"
}
const describe = (s: Status) =>
pipe(
s,
MMatch.make,
MMatch.whenIs(Status.A, () => "A status"),
MMatch.orElse(() => "other")
)
console.log(describe(Status.A)) // "A status"
Signature
export declare const whenIs: <Input extends MTypes.Primitive, Rest extends Input, const A extends Rest, Output1>(
value: A,
f: (value: A) => Output1
) => <Output>(self: Type<Input, Output, Rest>) => Type<Input, Output | Output1, Exclude<Rest, A>>
whenIsOr
Matches against multiple primitive values using strict equality. Returns a copy of self if self already has an output. Otherwise, self.input is compared to each provided value using strict equality. Returns a copy of self if no values match. Otherwise, returns a copy of self with the output set to the result of f(self.input).
- Use when matching against multiple literal values with the same handler.
- All values are compared using strict equality (
===). - Efficient alternative to chaining multiple
whenIscalls.
Example (Match against multiple values)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
enum Status {
A = "a",
B = "b",
C = "c"
}
const describe = (s: Status) =>
pipe(
s,
MMatch.make,
MMatch.whenIs(Status.A, () => "A"),
MMatch.whenIsOr(Status.B, Status.C, () => "B or C"),
MMatch.exhaustive
)
console.log(describe(Status.B)) // "B or C"
Signature
export declare const whenIsOr: <
Input extends MTypes.Primitive,
Rest extends Input,
R extends MTypes.ReadonlyOverTwo<Rest>,
Output1
>(
...args: readonly [...values: R, f: (value: R[number]) => Output1]
) => <Output>(self: Type<Input, Output, Rest>) => Type<Input, Output | Output1, Exclude<Rest, R[number]>>
whenOr
Matches using multiple predicates. The match occurs if one of the predicates returns true. Returns a copy of self if self already has an output. Otherwise, applies all predicates to self.input and returns a copy with the output set to f(self.input) if any predicate matches.
- Use when multiple unrelated conditions trigger the same handler.
- Efficient alternative to chaining multiple
whencalls with OR logic. - All predicates are evaluated (non-short-circuit).
Example (Match with multiple predicates)
import { pipe } from "effect"
import * as MMatch from "@parischap/effect-lib/MMatch"
import * as Number from "effect/Number"
const result = pipe(
5,
MMatch.make,
MMatch.whenOr(Number.isLessThan(0), Number.isGreaterThan(10), () => "out of range"),
MMatch.orElse(() => "in range")
)
console.log(result) // "in range"
Signature
export declare const whenOr: <
Input,
Rest extends Input,
R extends MTypes.ReadonlyOverTwo<Predicate.Predicate<Rest>>,
Output1
>(
...args: readonly [
...refinements: R,
f: (value: NoInfer<MTypes.IntersectAndSimplify<MPredicate.PredicatesToTargets<R>[number], Rest>>) => Output1
]
) => <Output>(
self: Type<Input, Output, Rest>
) => Type<Input, Output | Output1, Exclude<Rest, MPredicate.PredicatesToCoverages<R>[number]>>