TypeScript Academy · Урок

Обобщённые функторы и мапперы

Пишите функции map, обобщённые для любого контейнера

Урок 4 из 413 шагов

«Обобщённые функторы и мапперы» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Проблема HKT в TypeScript
  2. Определение конструкторов типов
  3. Облегчённый шаблон HKT
  4. Обобщённые функторы и мапперы
← Назад к TypeScript Academy