neverthrow icon indicating copy to clipboard operation
neverthrow copied to clipboard

More ergonomic `match`

Open braxtonhall opened this issue 2 years ago • 0 comments

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;
    // ...
  }

braxtonhall avatar Oct 09 '23 13:10 braxtonhall