Last active
January 10, 2023 13:32
-
-
Save KillyMXI/b5a046954d9c6f0780b8b9cf1fe4114d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { expectAssignable, expectType } from 'tsd'; | |
type Some<T> = { _some: true, value: T }; | |
type None = { _some: false }; | |
type Option<T> = Some<T> | None; | |
type GetSome<T,R> = (input: T) => Some<R>; | |
type GetOption<T,R> = (input: T) => Option<R>; | |
function isSome<T>(option: Option<T>): option is Some<T> { return option._some; } | |
const liftSome = <T>(x: T) => ({ _some: true, value: x } as Some<T>); | |
const none: None = { _some: false }; | |
const id = <T>(x: T) => x; | |
// return type narrowing via overloading | |
function mapOptionA<T,R1,R2>(getter: GetSome<T,R1>, mapper: (input: R1) => R2): GetSome<T,R2>; | |
function mapOptionA<T,R1,R2>(getter: GetOption<T,R1>, mapper: (input: R1) => R2): GetOption<T,R2>; | |
function mapOptionA<T,R1,R2>(getter: GetOption<T,R1>, mapper: (input: R1) => R2): GetOption<T,R2> { | |
return ((input: T) => { | |
const r1 = getter(input); | |
return isSome(r1) ? liftSome(mapper(r1.value)) : none; | |
}); | |
} | |
// return type narrowing via type inference | |
function mapOptionB< | |
TGetter1 extends GetOption<T,R1>, | |
TMapper extends (input: R1) => R2, | |
T = TGetter1 extends GetOption<infer T,any> ? T : never, | |
R1 = TGetter1 extends GetOption<any,infer R> ? R : never, | |
R2 = ReturnType<TMapper>, | |
TGetter2 = TGetter1 extends GetSome<T,R1> ? GetSome<T,R2> : GetOption<T,R2> | |
>( | |
getter: TGetter1, | |
mapper: TMapper | |
) { | |
return ((input: T) => { | |
const r1 = getter(input); | |
return isSome(r1) ? liftSome(mapper(r1.value)) : none; | |
}) as TGetter2; | |
} | |
function callerFunctionC<R> (getter: GetOption<number,R>) { | |
const x = getter(42); | |
return x; | |
} | |
function callerFunctionD< | |
TGetter1 extends GetOption<number,R>, | |
R = TGetter1 extends GetOption<any,infer R1> ? R1 : unknown, | |
TResult = TGetter1 extends GetSome<number,R> ? Some<R> : Option<R> | |
> (getter: TGetter1) { | |
const x = getter(42); | |
return x as TResult; | |
} | |
const mappedA = mapOptionA(liftSome, id); | |
const mappedB = mapOptionB(liftSome, id); | |
expectAssignable<GetOption<number,number>>( mappedA ); // pass | |
expectAssignable<GetOption<number,number>>( mappedB ); // Argument of type 'GetSome<unknown, unknown>' is not assignable to parameter of type 'GetOption<number, number>' | |
// ^ this seems to be the key difference. | |
// types are resolved to `unknown` at the end of statement. | |
// What I would like to is to infer a type that is still generic, same as overload does... | |
expectAssignable<GetOption<number,number>>( mapOptionA(liftSome, id) ); // pass | |
expectAssignable<GetOption<number,number>>( mapOptionB(liftSome, id) ); // pass | |
expectType<GetSome<number,number>>( mapOptionA(liftSome, id) ); // pass | |
expectType<GetSome<number,number>>( mapOptionB(liftSome, id) ); // pass | |
const resultAC1 = callerFunctionC(mapOptionA(liftSome, id)); | |
const resultBC1 = callerFunctionC(mapOptionB(liftSome, id)); | |
const resultAD1 = callerFunctionD(mapOptionA(liftSome, id)); | |
const resultBD1 = callerFunctionD(mapOptionB(liftSome, id)); | |
expectAssignable<Option<number>>( resultAC1 ); // pass | |
expectAssignable<Option<number>>( resultBC1 ); // Argument of type 'Option<unknown>' is not assignable to parameter of type 'Option<number>' | |
expectAssignable<Option<number>>( resultAD1 ); // Argument of type 'Some<unknown>' is not assignable to parameter of type 'Option<number>' | |
expectAssignable<Option<number>>( resultBD1 ); // Argument of type 'Some<unknown>' is not assignable to parameter of type 'Option<number>' | |
expectAssignable<Option<number>>( callerFunctionC(mapOptionA(liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionC(mapOptionB(liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionD(mapOptionA(liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionD(mapOptionB(liftSome, id)) ); // pass | |
const resultAC2 = callerFunctionC(mapOptionA(<GetSome<number,number>>liftSome, id)); | |
const resultBC2 = callerFunctionC(mapOptionB(<GetSome<number,number>>liftSome, id)); | |
const resultAD2 = callerFunctionD(mapOptionA(<GetSome<number,number>>liftSome, id)); | |
const resultBD2 = callerFunctionD(mapOptionB(<GetSome<number,number>>liftSome, id)); | |
expectAssignable<Option<number>>( resultAC2 ); // pass | |
expectAssignable<Option<number>>( resultBC2 ); // Argument of type 'Option<unknown>' is not assignable to parameter of type 'Option<number>' | |
expectAssignable<Option<number>>( resultAD2 ); // pass | |
expectAssignable<Option<number>>( resultBD2 ); // Argument of type 'Some<unknown>' is not assignable to parameter of type 'Option<number>' | |
expectAssignable<Option<number>>( callerFunctionC(mapOptionA(<GetSome<number,number>>liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionC(mapOptionB(<GetSome<number,number>>liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionD(mapOptionA(<GetSome<number,number>>liftSome, id)) ); // pass | |
expectAssignable<Option<number>>( callerFunctionD(mapOptionB(<GetSome<number,number>>liftSome, id)) ); // pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment