neverthrow
neverthrow copied to clipboard
More ergonomic `match`
When writing a match in neverthrow, the ok and err callbacks must have the same ReturnType in their signature. This means the following code results in an error
declare const result: Result<string, number>
const matchResult = result.match(
(value: string) => !!value,
(value: number) => BigInt(10),
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// TS2345: Argument of type (value: number) => bigint is not assignable to parameter of type (e: number) => boolean
// Type bigint is not assignable to type boolean
)
This can be fixed by annotating the callbacks or the match application with more generic types
declare const result: Result<string, number>
const matchResultA = result.match(
(value: string): boolean | bigint => !!value,
(value: number) => BigInt(10),
)
const matchResultB = result.match<boolean | bigint>(
(value: string) => !!value,
(value: number) => BigInt(10),
)
However this is pretty inconvenient!
Instead, match can infer that the result is the union of whatever ok returns, and whatever err returns
interface Result<T, E> {
// ...
- match<A>(ok: (t: T) => A, err: (e: E) => A): A;
+ match<A, B>(ok: (t: T) => A, err: (e: E) => B): A | B;
// ...
}
declare const result: Result<string, number>
const matchResult = result.match(
(value: string) => !!value,
(value: number) => BigInt(10),
)
true satisfies typeof matchResult
BigInt(10) satisfies typeof matchResult
Unfortunately, this would be a breaking change! Anyone who had already annotated their method call would now get an error
declare const result: Result<string, number>
const matchResult = result.match<boolean | bigint>(
// ~~~~~~~~~~~~~~~~
// TS2558: Expected 2 type arguments, but got 1
(value: string) => !!value,
(value: number) => BigInt(10),
)
To support legacy code, we can add a default to the second type argument
interface Result<T, E> {
// ...
- match<A>(ok: (t: T) => A, err: (e: E) => A): A;
+ match<A, B = A>(ok: (t: T) => A, err: (e: E) => B): A | B;
// ...
}