Skip to content

Instantly share code, notes, and snippets.

@KillyMXI
Last active January 10, 2023 13:32
Show Gist options
  • Save KillyMXI/b5a046954d9c6f0780b8b9cf1fe4114d to your computer and use it in GitHub Desktop.
Save KillyMXI/b5a046954d9c6f0780b8b9cf1fe4114d to your computer and use it in GitHub Desktop.
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