An educational pure functional programming library in TypeScript
1import { match } from "../prelude/match"
2
3/**
4 * Exit represents the result of an effect execution.
5 * Three variants: Success, Failure, or Interrupted.
6 */
7export type Exit<A, E> =
8 | { readonly _tag: "Success"; readonly value: A }
9 | { readonly _tag: "Failure"; readonly error: E }
10 | { readonly _tag: "Interrupted"; readonly by: string }
11
12// Constructors
13export const success = <A>(value: A): Exit<A, never> => ({
14 _tag: "Success",
15 value,
16})
17
18export const failure = <E>(error: E): Exit<never, E> => ({
19 _tag: "Failure",
20 error,
21})
22
23export const interrupted = (by: string): Exit<never, never> => ({
24 _tag: "Interrupted",
25 by,
26})
27
28// Type guards
29export const isSuccess = <A, E>(
30 exit: Exit<A, E>,
31): exit is Extract<Exit<A, E>, { _tag: "Success" }> => exit._tag === "Success"
32
33export const isFailure = <A, E>(
34 exit: Exit<A, E>,
35): exit is Extract<Exit<A, E>, { _tag: "Failure" }> => exit._tag === "Failure"
36
37export const isInterrupted = <A, E>(
38 exit: Exit<A, E>,
39): exit is Extract<Exit<A, E>, { _tag: "Interrupted" }> =>
40 exit._tag === "Interrupted"
41
42// Match helper
43export const matchExit =
44 <A, E, R>(
45 onSuccess: (value: A) => R,
46 onFailure: (error: E) => R,
47 onInterrupted: (by: string) => R,
48 ) =>
49 (exit: Exit<A, E>): R =>
50 match(exit)({
51 Success: ({ value }) => onSuccess(value),
52 Failure: ({ error }) => onFailure(error),
53 Interrupted: ({ by }) => onInterrupted(by),
54 })
55
56/**
57 * Transform both success and failure values of an Exit (Bifunctor).
58 * Interrupted exits pass through unchanged.
59 *
60 * @example
61 * ```typescript
62 * pipe(
63 * success(10),
64 * bimapExit(
65 * n => n * 2, // transform Success
66 * e => e.toUpperCase() // transform Failure
67 * )
68 * ) // Success(20)
69 * ```
70 */
71export const bimapExit =
72 <A, B, E, F>(onSuccess: (a: A) => B, onFailure: (e: E) => F) =>
73 (exit: Exit<A, E>): Exit<B, F> =>
74 exit._tag === "Success"
75 ? success(onSuccess(exit.value))
76 : exit._tag === "Failure"
77 ? failure(onFailure(exit.error))
78 : interrupted(exit.by)
79
80// Namespace for convenient dotted access to the Exit API.
81export const Exit = {
82 success,
83 failure,
84 interrupted,
85 isSuccess,
86 isFailure,
87 isInterrupted,
88 match: matchExit,
89 bimap: bimapExit,
90}