Освой карринг и шаблоны TypeScript быстро и безболезненно

Что за страшный зверь этот ваш карринг? Говорим о набирающем популярность TypeScript, рассказываем и показываем на нем же.

Освой карринг и шаблоны TypeScript быстро и безболезненно

Ты научишься создавать типы для карринга и Ramda. Для следования этому гайду желателен опыт работы с примитивными типами TypeScript. К концу ты узнаешь, как создавать мощные типы вроде этого:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Карринг, что ты такое?

Карринг или каррирование – это процесс преобразования функции, которая принимает несколько аргументов, в серию функций, которые принимают один аргумент за раз.

Вот функция, которая принимает два числа и возвращает их сумму:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Каррированная версия simpleAdd выглядит так:

Освой карринг и шаблоны TypeScript быстро и безболезненно

В этом руководстве сначала рассмотрим, как создавать типы TypeScript, которые работают со стандартной реализацией карринга.

Затем мы будем развивать их в продвинутые типы, что позволяют каррированным функциям принимать 0 или более аргументов.

Карринг v0

Каррированная функция:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Наш первый тип карринга принимает кортеж параметров P и возвращаемый тип R. Это тип рекурсивной функции, который зависит от длины P:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Если HasTail сообщает false – все параметры использованы и пришло время вернуть тип R из оригинальной функции. Иначе, если остаются параметры для использования, мы выполняем рекурсию внутри типа. CurryV0 описывает функцию с возвращаемым типом CurryV0, пока существует Tail (HasTail<P> extends true).

Вот доказательство без какой-либо реализации:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Представим рекурсию выше:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Подсказки типов работают для неограниченного количества параметров:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Карринг v1

Мы забыли обработать сценарий, в котором передаются оставшиеся параметры:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Мы попытались использовать оставшиеся параметры, но это не сработает, так как ожидается один параметр или аргумент, который мы назвали arg0. Так, нужно получить хотя бы один аргумент arg0 и любые дополнительные (необязательные) аргументы внутри оставшегося параметра rest. Включаем остальные параметры с помощью Tail и Partial:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Ужасная ошибка! Аргументы обрабатываются очень плохо, а TS молчит :(

Освой карринг и шаблоны TypeScript быстро и безболезненно

Это проблема проектирования, которая возникает из-за единственного принимаемого аргумента arg0. Нужно отслеживать аргументы, которые используются одновременно. Избавимся от arg0 и начнем отслеживать используемые параметры:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Но теперь мы потеряли проверку типов, потому что указали отслеживание любых [] параметров. Но дело не только в этом. Теперь использование Tail бессмысленно, потому что Tail работает, когда принимается один аргумент за раз.

Нужно больше инструментов!

Рекурсивные типы

Рассмотрим инструменты для определения параметров. Отслеживая используемые параметры с помощью T, мы сможем угадать оставшиеся параметры.

Пристегните ремни! Очередная мощная техника прямо по курсу:

Last

Этот тип принимает кортеж в качестве параметра и извлекает последнюю запись:

Освой карринг и шаблоны TypeScript быстро и безболезненноДавайте проверим:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Основные инструменты №1

Где мы? Нам нужны инструменты для отслеживания аргументов, помните? А значит, нужно знать, какие типы параметров можно использовать, какие из них были использованы, и какие будут следующими. Приступим!

Length

Для анализа выше нужно выполнить итерации по кортежам. В TypeScript 3.4.x нет аналога for. В идеале нужен счетчик:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверяем:

Освой карринг и шаблоны TypeScript быстро и безболезненноНаполняя кортеж типом any, мы создали нечто похожее на переменную, которую можно увеличивать. Length просто задает размер кортежа и работает с любым другим типом кортежа:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Prepend

Prepend добавляет тип E поверх кортежа T:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверка:

Освой карринг и шаблоны TypeScript быстро и безболезненноВ примере с Length мы увеличивали счетчик вручную. Prepend – идеален как основа для счетчика. Вот так он работает:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Drop

Drop принимает кортеж T и удаляет первые N записей. Для этого используем те же приемы, что и в Last:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверка:

Освой карринг и шаблоны TypeScript быстро и безболезненноDrop будет повторяться до тех пор, пока Length<I> не совпадет со значением N, которое мы передали. Другими словами, тип индекса 0 выбирается условным методом доступа до тех пор, пока это условие не будет выполнено. + мы использовали Prepend, чтобы увеличить счетчик, как в цикле. Так что Length<I> используется в качестве счетчика рекурсии, и это способ свободной итерации в TS.

Карринг v2

Ты проделал нелегкий путь, добравшись сюда, и это здорово!

Предположим, теперь мы можем отследить, как 2 параметра используются каррированной функцией:

Освой карринг и шаблоны TypeScript быстро и безболезненноС Drop узнаем количество употребленных и не использованных параметров:

Освой карринг и шаблоны TypeScript быстро и безболезненноОбновим предыдущую версию со сломанным Tail:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Что же мы сделали?

Во-первых, Drop<Length<T>, P> означает удаление употребленных параметров. Затем, если длина Drop<Length<T>, P> не равна 0, тип карринга должен продолжать рекурсию с отброшенными параметрами, пока... Наконец, когда все параметры употреблены, Length отброшенных параметров равна 0, а возвращаемый тип – R.

Карринг v3

Есть еще одна ошибка выше: TS жалуется, что Drop не относится к типу []. Иногда TS жалуется на неожиданный тип, несмотря на то, что он подходит! Это повод добавить еще один инструмент в коллекцию:

Cast

Cast требует, чтобы TS перепроверил тип X с типом Y, и тип Y будет применен только в случае неудачи. Так можно предотвратить жалобы TS:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

И вот предыдущий карринг без каких-либо жалоб:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Карринг v4

Мы все еще не можем взять оставшиеся параметры. И вот почему:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Поскольку количество оставшихся параметров может быть неограниченными, TS полагает, что длина нашего кортежа – это число number. Поэтому нельзя использовать Length при работе с оставшимися параметрами, что не так плохо:

Освой карринг и шаблоны TypeScript быстро и безболезненно

При использовании параметров, Drop<Length<T>,P> может проверять только […any[]]. Благодаря этому мы использовали [any,…any[]] в качестве условия для завершения рекурсии.

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Все работает! Теперь у тебя есть универсальный, вариативный тип карринига. Как насчет дальнейших улучшений?

Плейсхолдеры

Предоставим нашему типу способность понимать частичное применение любой комбинации аргументов, в любой позиции. Согласно документации Ramda, мы можем сделать это, используя плейсхолдер _. В ней говорится, что эти вызовы эквивалентны любой каррированной функции f:

Освой карринг и шаблоны TypeScript быстро и безболезненноПлейсхолдер или «пробел» – это объект, который абстрагирует факт отсутствия аргумента для передачи в определенный момент. Начнем с определения плейсхолдера. Обратимся напрямую к Ramda:

Освой карринг и шаблоны TypeScript быстро и безболезненноМы уже знаем, как выполнять первые итерации типов, увеличивая длину кортежа. Но использование Length и Prepend для нашего типа счетчика не вносит ясности. С этого момента мы будем обращаться к счетчику как к итератору. Вот новые псевдонимы для этого:

Pos (Позиция)

Используйте его для запроса позиции итератора:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Next (+1)

Поднимает позицию итератора:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Prev (-1)

Снижает позицию итератора:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Итератор

Он создает итератор (наш тип счетчика) в позиции, определяемой Index, и может начинать с позиции другого итератора, используя From:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Основные инструменты №2

Отлично, что делаем дальше? Нужно проанализировать передачу плейсхолдера в качестве аргумента. Так мы поймем, был параметр «пропущен» или «отложен». Вот инструменты для этой цели:

Reverse

Reverse даст необходимую свободу. Он принимает кортеж T и превращает его в кортеж R благодаря новым типам итераций:

Освой карринг и шаблоны TypeScript быстро и безболезненноПроверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Concat

И родился из Reverse Concat. Он объединяет кортежи T1 и T2. Мы делали это в test59:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Append

С помощью Concat Append может добавить тип E в конец кортежа T:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверка:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Карринг v5

Вот и готовы инструменты для типа каррирования. Gaps – это новая замена Partial, а GapsOf заменит Drop:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Проверим:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Для проверки принудительно установим значения, которые будут взяты с каррированной функцией:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Упс! Маленькая проблема. Дело в том, что мы «опередили» Ramda! Наш тип понимает сложные использования плейсхолдеров. Другими словами, плейсхолдеры Ramda просто не работают с оставшимися параметрами:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Освой карринг и шаблоны TypeScript быстро и безболезненно

Несмотря на то, что все все выглядит правильно, мы получим падение. Реализация карринга Ramda не справляется с комбинацией плейсхолдеров и оставшихся параметров.

Карринг

Осталось решить последнюю проблему с подсказками параметров. Полезно знать названия параметров, с которыми работаешь. Версия выше не допускает такого рода подсказок. Вот исправление:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Мы получили подсказки для Visual Studio Code. Как? Да просто заменили типы параметров P иR, которые использовались для обозначения типов параметра и возврата. Вместо этого мы использовали тип функции F, из которого извлекли эквивалент Parameters<F> и R с ReturnType<F>. Так, TypeScript способен сохранять имя параметров даже после каррирования:

Освой карринг и шаблоны TypeScript быстро и безболезненно

Один нюанс: при использовании пробелов имя параметра теряется.

Источник

Понравилась статья? Читайте также:

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Java Team Lead
Москва, по итогам собеседования
Go-разработчик
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ