Обобщённые функторы и мапперы
Пишите функции map, обобщённые для любого контейнера
«Обобщённые функторы и мапперы» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.
Универсальный функтор
Теперь мы используем кодирование по назначению: создадим универсальный map, работающий с любым зарегистрированным контейнером. Интерфейс Functor, параметризованный URI, объявляет один метод.
interface Functor<F extends URIS> {
readonly URI: F;
map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}Экземпляр массива
Экземпляр реализует map для одного URI. Экземпляр массива выполняет отображение с помощью встроенного метода массива. Его Kind<"Array", A> разрешается в A[].
const arrayFunctor: Functor<"Array"> = {
URI: "Array",
map: (fa, f) => fa.map(f)
};Использование экземпляра массива
Вызов map у экземпляра массива преобразует каждый элемент, сохраняя тип контейнера-массива.
const doubled = arrayFunctor.map([1, 2, 3], n => n * 2);
// doubled: number[] -> [2, 4, 6]Тип опции
Определите опцию как значение или null и зарегистрируйте её. Её map применяет функцию только при наличии значения, а в противном случае передаёт дальше null.
interface URItoKind<A> { Option: A | null }
type Option<A> = A | null;Экземпляр опции
Экземпляр опции сохраняет контейнер-опцию: функция применяется к значению, а null остаётся null.
const optionFunctor: Functor<"Option"> = {
URI: "Option",
map: (fa, f) => (fa === null ? null : f(fa))
};Использование экземпляра опции
Теперь та же форма map работает с совершенно другим контейнером. Присутствующие значения преобразуются, а отсутствующие остаются без изменений.
const a = optionFunctor.map(5, n => n + 1); // 6
const b = optionFunctor.map(null, n => n + 1); // nullОдна абстракция, много контейнеров
В этом и состоит преимущество. arrayFunctor и optionFunctor используют один и тот же интерфейс Functor. Код, написанный для Functor<F>, работает с обоими без изменений.
function bumpAll<F extends URIS>(
F: Functor<F>,
fa: Kind<F, number>
): Kind<F, number> {
return F.map(fa, n => n + 1);
}Вызов универсальной функции
bumpAll не знает и не заботится о том, какой контейнер получает. Передайте экземпляр и данные — результат сохранит тип контейнера.
const arr = bumpAll(arrayFunctor, [1, 2]); // number[] -> [2, 3]
const opt = bumpAll(optionFunctor, 9); // number | null -> 10Законы функтора
Корректный функтор подчиняется двум законам: отображение тождественной функцией ничего не меняет, а последовательное отображение двух функций эквивалентно отображению их композиции. Оба приведённых выше экземпляра — массива и опции — удовлетворяют этим законам.
// map(fa, x => x) === fa
// map(map(fa, f), g) === map(fa, x => g(f(x)))Добавление новых экземпляров
Поскольку абстракция открыта, новые контейнеры подключаются регистрацией URI и предоставлением экземпляра. Дерево, результат или задача — каждый из них можно преобразовывать с помощью одного и того же универсального кода.
// Register URI, implement Functor<"Tree">,
// and bumpAll works on trees too - no edits to bumpAllПочему это важно
Универсальные функторы — отправная точка для функциональных абстракций в TypeScript: аппликативов, монад и обходов. Каждый из них — интерфейс над Kind<F, A> с экземплярами для отдельных контейнеров. Теперь вы понимаете механизм, делающий возможными fp-ts и Effect.
interface Monad<F extends URIS> extends Functor<F> {
of<A>(a: A): Kind<F, A>;
chain<A, B>(fa: Kind<F, A>, f: (a: A) => Kind<F, B>): Kind<F, B>;
}Быстрая проверка
Проверьте, насколько хорошо вы понимаете универсальные функторы.
Повторение
Вы написали map, универсальный для контейнеров.
Functor<F>объявляетmapнадKind<F, A>.- Экземпляры массива и опции реализуют его для своих URI.
- Универсальный код вроде
bumpAllработает с любым зарегистрированным контейнером. - Законы функтора обеспечивают корректное поведение экземпляров, а более богатые абстракции строятся на этой основе.
Далее, в курсе 24: создание анализаторов с типобезопасностью.
Изучай TypeScript с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 101
- Уроки
- 352
Часто задаваемые вопросы
Урок «Обобщённые функторы и мапперы» бесплатный?
Да — полный текст урока «Обобщённые функторы и мапперы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.
Чему я научусь в уроке «Обобщённые функторы и мапперы»?
Пишите функции map, обобщённые для любого контейнера Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Обобщённые функторы и мапперы»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проблема HKT в TypeScript
- Определение конструкторов типов
- Облегчённый шаблон HKT
- Обобщённые функторы и мапперы