В TypeScript нет настоящих Higher-Kinded Types (HKT).
А они применяются в FP - вот и ищут люди способы эмулировать.
На данный момент мне известно о двух способах
Первый называется дефункционализация и вот пример его реализации
// lib
interface URIToValue<out A> {
// Id: Container<A>
}
type URIS = keyof URIToValue<any>;
type Apply<URI extends URIS, A> = URIToValue<A>[URI];
interface Functor<URI extends URIS> {
map<A, B>(fa: Apply<URI, A>, f: (a: A) => B): Apply<URI, B>;
}
// userland Option<T>
type Option<A> = { _tag: "Some"; value: A } | { _tag: "None" };
declare module 'lib' {
interface URIToValue<out A> {
Option: Option<A>;
}
}
const optionFunctor: Functor<"Option"> = {
map(fa, f) {
//^? Option<A>
return fa._tag === "Some" ? { _tag: "Some", value: f(fa.value) } : fa;
},
};
// userland Array<T>
declare module 'lib' {
interface URIToValue<out A> {
Array: Array<A>;
}
}
const arrayFunctor: Functor<"Array"> = {
map(fa, f) {
//^? Array<A>
return fa.map(f);
},
};
Тут идея такова что мы каждый тип данных регистрируем с помощью аугментации модулей и слияния интерфейсов в реестре
URIToValue и добываем настоящий тип с помощью Apply<Id, Value>Вроде бы окей но сложности начинаются при попытке написать универсальную композицию функторов:
// Option + Array = Option<Array<T>>
// Array + Option = Array<Option<T>>
function compose<URI_outer extends URIS, URI_inner extends URIS>(
outer: Functor<URI_outer>,
inner: Functor<URI_inner>,
): Functor<URI_outer + URI_inner> {
return {
map(f_outer_inner, f) {
return outer.map(f_outer_inner, (f_inner) => inner.map(f_inner, f));
}
}
}
Чтобы сделать Apply в функции
Functor.map - нам нужно ЗАРАНЕЕ зарегистрировать тип данных в реестре URIToValue - на лету не получится (`Functor<URI_outer + URI_inner>`)И еще одна задачка появляется при композиции функторов разной арности
например хочу сделать
Either + Array / Array + Either — возникнет проблема с тем как хранить и передавать E. И нужно будет добавить перегрузку compose21 (`Either<Array<A>, E>`), `compose12`(`Array<Either<A, E>>`) и т.д.Дальше посмотрим на второй способ реализации HKT с помощью
TypeLambda подхода и как этот способ решает проблемы универсальной композиции