Проблема HKT в TypeScript
Поймите, почему обобщённые конструкторы типов трудно реализовать
«Проблема HKT в TypeScript» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.
Что такое тип высшего рода
Тип высшего рода (HKT) абстрагируется не от конкретного типа, а от конструктора типов: например, от Array или Promise, которым нужен аргумент, чтобы стать настоящим типом.
Сам по себе Array не является типом. Типом является Array<string>. Array — это функция над типами, а HKT позволяют обобщать такие функции.
type StringArray = Array<string>;
type NumberArray = Array<number>;
// Array by itself is a type constructor, not a typeКоротко о родах
Подобно тому как значения имеют типы, конструкторы типов имеют роды. Обычный тип вроде number имеет род *. Array имеет род * -> *: передайте ему один тип и получите тип в ответ. Обобщение по таким конструкторам и называется «высшим родом».
type ValueLike = number; // kind *
// Array needs one argument: kind * -> *
// Map needs two arguments: kind * -> * -> *Цель: Functor<F>
В языках с поддержкой HKT можно написать единую абстракцию Functor<F> с одним методом map, который работает с любым контейнером F — массивом, вариантом или обещанием. Один интерфейс, множество контейнеров.
interface Wish_Functor<F> {
map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
}
// F<A> here is the wish - and TypeScript rejects itПроблема
TypeScript не поддерживает применение параметра типа к аргументу. Нельзя написать F<A>, когда F является обобщённым параметром. Компилятор сообщает об ошибке «Тип F не является обобщённым».
// interface Functor<F> {
// map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
// }
// Error: Type F is not genericПочему в TS этого нет
Параметры типов в TypeScript имеют порядок первого уровня: они задают конкретные типы, а не конструкторы типов. Не существует синтаксиса, позволяющего сказать: «F — это нечто, принимающее один аргумент типа». Поэтому F<A> не имеет смысла для компилятора.
// You can pass number, string, User as F...
// but not Array, not Promise (the constructors themselves)Конкретная проблема
Без HKT приходится дублировать map для каждого контейнера: один вариант для массивов, один для опций, один для промисов. Логика идентична, но система типов не может объединить их.
declare function mapArray<A, B>(fa: A[], f: (a: A) => B): B[];
declare function mapOption<A, B>(fa: A | null, f: (a: A) => B): B | null;
// Same shape, but no way to abstract over the containerЧто мы хотим выразить
Нам нужна одна сигнатура: если даны контейнер типа A и функция A -> B, нужно вернуть контейнер типа B, сохранив тип контейнера. Массив остаётся массивом, опция — опцией.
// Wish: map<F, A, B>(fa: F<A>, f: (a: A) => B): F<B>
// where F is preserved across the callДругие языки
В Haskell и Scala HKT поддерживаются изначально, поэтому классы типов вроде Functor, Applicative и Monad можно выразить непосредственно. В TypeScript нужен обходной путь, приближающийся к той же выразительной силе.
// Haskell: class Functor f where fmap :: (a -> b) -> f a -> f b
// TypeScript: needs an encoding trick (next lessons)Идея кодирования
Стандартный обходной путь — это дефункционализация: вместо непосредственного применения F мы присваиваем каждому контейнеру строковый тег (URI) и храним реестр, сопоставляющий тег и аргумент с конкретным типом. Применение типа превращается в поиск.
// Instead of F<A>, we write Kind<F, A> where F is a URI string
// and a registry resolves it to the real typeЧто вы создадите
В следующих уроках вы шаг за шагом создадите кодирование HKT в стиле fp-ts: реестр URItoKind, поиск Kind и наконец универсальный Functor, чей map работает с любым зарегистрированным контейнером.
// Roadmap:
// 1. Type constructors via URI strings
// 2. Kind<F, A> resolution
// 3. Generic Functor + instancesПочему это стоит знать
Вам редко придётся писать такое кодирование самостоятельно, но библиотеки fp-ts и Effect опираются на него. Понимание этого приёма позволит вам читать их типы, писать универсальные абстракции над контейнерами и лучше понимать возможности и ограничения системы типов.
// Recognizing Kind<F, A> in library code is the payoffБыстрая проверка
Проверьте, насколько хорошо вы понимаете проблему HKT.
Повторение
Вы познакомились с проблемой типов высшего порядка.
- HKT абстрагируют конструкторы типов вроде
Array, а не конкретные типы. - Параметры типов в TypeScript относятся к первому порядку, поэтому
F<A>недопустим. - Из-за этого нельзя создать один универсальный
Functorс однимmap. - Решение — кодирование с использованием URI-тегов и реестра.
Далее: определение конструкторов типов с помощью строк URI.
Часто задаваемые вопросы
Урок «Проблема HKT в TypeScript» бесплатный?
Да — полный текст урока «Проблема HKT в TypeScript» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.
Чему я научусь в уроке «Проблема HKT в TypeScript»?
Поймите, почему обобщённые конструкторы типов трудно реализовать Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Проблема HKT в TypeScript»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проблема HKT в TypeScript
- Определение конструкторов типов
- Облегчённый шаблон HKT
- Обобщённые функторы и мапперы