HKT на основе TypeLambda
interface TypeLambda {
readonly Arg1: unknown;
readonly Arg2: unknown;
readonly Arg3: unknown;
readonly Return: unknown;
}
type Call<TL extends TypeLambda, A1, A2, A3> = (TL & {
readonly Arg1: A1;
readonly Arg2: A2;
readonly Arg3: A3;
})["Return"];
interface MyEffect<A, E, R> {
(r: R): Promise<A | E>;
}
interface MyEffectLambda extends TypeLambda {
Return: MyEffect<this["Arg1"], this["Arg2"], this["Arg3"]>;
}
type Result = Call<MyEffectLambda, 1, 2, 3>;
// ^? MyEffect<1, 2, 3>
Реализация очень простая:
Основной трюк - это полиморфный
thisБлагодаря ему MyLambda и выглядит как самостоятельная функция - взять переданные аргументы и как-то их заколбасить. В
Call<...> видно, что мы передаем лямбду и типо-аргументы, далее пересекаем TL & {...Args} (это и есть момент передачи аргументов) - после чего polymorphic this начинает видеть конкретные типы. Наконец, просто забираем результат через ['Return'] - очень просто и изящно.Так же в отличие от предыдущего примера это легче масштабируется - нужен еще один параметр? просто добавьте
Arg4 в TypeLambda и существующие лямбды не надо исправлять - Arg4 в них просто игнорируетсяЧто на счет разной разрядности типов - метод
Call<> выглядит одинаково как для Array<T> так и для Either<A, E>
interface ArrayLambda extends TypeLambda {
Return: Array<this['Arg1']>
}
interface EitherLambda extends TypeLambda {
Return: Either<this['Arg1'], this['Arg2']>
}
type ArrayResult = Call<ArrayLambda, number, void, void>
// ^? Array<number>
type EitherResult = Call<EitherLambda, number, string, void>
// ^? Either<number, string>
Ещё есть такое понятие, как натуральная трансформация. Это когда мы хотим заменить один функтор на другой, не изменяя его содержимое. Например, преобразовать
Either<A, E> в Option<A>, сохранив тот же A.
interface OptionLambda extends TypeLambda {
Return: Option<this["Arg1"]>;
}
type NaturalTransformation<F extends TypeLambda, G extends TypeLambda> = <A1, A2, A3>(fa: Call<F, A1, A2, A3>) => Call<G, A1, A2, A3>;
type nt = NaturalTransformation<MyEffectLambda, OptionLambda>;
// ^? <A1, A2, A3>(fa: MyEffect<A1, A2, A3>) => Option<A1>
Вот так она просто выражается в энкодинге на
TypeLambda - в случае с дефункционализацией пришлось бы городить все возможные перегрузки чтобы сочетать (`URI2<A, B>` и URI<A> и URI3<A, B, C>`) собсна вот в `fp-ts https://gcanti.github.io/fp-ts/modules/NaturalTransformation.ts.htmlhttps://tsplay.dev/Nl4PrN