I'm currently experimenting with adapting Scott Wlaschin's concept of "Railway Oriented Programming" to Typescript.
I am focusing on ensuring the correct types for the either
function. Although I believe my code provided below should be functional, I am encountering compiler errors when it comes to the return statements within the either
function.
type Success<T> = {
success: true,
result: T
};
type Failure<E> = {
success: false,
cause: E
}
export function either<T, E, U, F, I extends Success<T> | Failure<E>>(
onSuccess: (i: T) => U,
onFailure: (i: E) => F,
input: I
): I extends Success<T> ? U : I extends Failure<E> ? F : never {
if (input.success === true) {
return onSuccess(input.result); // encountering compiler error: Type 'U' is not assignable to type 'I extends Success<T> ? U : I extends Failure<E> ? F : never'
} else {
return onFailure(input.cause); // encountering compiler error: Type 'F' is not assignable to type 'I extends Success<T> ? U : I extends Failure<E> ? F : never'
}
}
The reason behind this error remains unclear to me. The conditional type indicates that both U and F can be returned, and my code successfully narrows down the type of the input argument. One way to resolve this issue is by utilizing a Type Assertion matching the return type of the function (e.g.
return onSuccess(input.result) as I extends Success<T> ? ....
), but my queries are:
- Is employing a Type Assertion the sole solution to eliminate the compiler errors?
- If so, why does Typescript struggle to recognize that the return values fulfill the specified return type?