Что такое карри? Практические примеры каррирования в JavaScript. Почему каррирование полезно в программировании. Как создавать каррированные функции.

Вы заметили, что из всех классических методов Array в JavaScript для итерации и выполнения некоторых преобразований этих данных, таких как map и forEach, никогда не понимается reduce?

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

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

"Привет, мир"

Мы должны с чего-то начать, верно?

Самый простой пример каррирования отлично подходит для понимания сути этой техники.

Видите ли, каррирование — это не что иное, как способ преобразования функций, принимающих несколько параметров, в последовательный список функций, каждая из которых принимает только один параметр.

Это бесполезный пример «привет, мир». «Волшебная» функция curry преобразует нашу функцию sum в функцию, которая принимает только один параметр и возвращает другую функцию, которая снова принимает один параметр и возвращает сумму обоих чисел.

Сложно реализовать? Черт возьми нет! В JavaScript это относительно тривиально:

Там у вас есть 2 способа реализовать это, хотя они будут работать только для функций с 2 ​​аргументами. Позже мы можем побеспокоиться о том, чтобы сделать его более общим.

Дело в том, что мы превратили функцию в список частичных функций. Обратите внимание на термин «частичные функции», потому что мы будем часто его использовать.

Давайте рассмотрим более подходящий пример: ведение журнала!.

Ведение журнала с помощью каррированной функции

Теперь давайте предположим, что у нас есть волшебная функция curry, которая берет любую функцию и корректно преобразует ее в список функций, каждая из которых принимает только один параметр. Я покажу вам, как это реализовать позже, не волнуйтесь.

Ведение журнала — это то, что мы делаем очень часто, во всех наших приложениях у нас есть какая-то функция ведения журнала, которая помогает нам отлаживать проблемы. Иногда это принимает форму console.log , иногда это Winston или аналогичный пакет. Дело в том, что мы получаем функцию-оболочку, которая выглядит так:

Конечно, у вас может быть его вариант или оболочка поверх этого, чтобы упростить синтаксис ведения журнала.

Цель использования каррирования здесь — дать нам немного синтаксического сахара, когда дело доходит до вызова нашей функции ведения журнала. И да, синтаксический сахар на самом деле хорошая вещь, он может помочь сделать наш код более читабельным и простым для понимания.

Давайте посмотрим, как только мы каррируем нашу функцию log, мы получим что-то вроде этого:

Какой смысл это делать? Дело в том, что мы можем использовать частичные функции, чтобы получить более читаемый и, что очень важно, повторно используемый код. Представьте, что вам нужно написать 100 строк журнала отладки по всему коду, вы бы предпочли использовать этот синтаксис или этот:

Используя каррирование, я предварительно заполнил 2 из 4 параметров, которые получает log. Я нигде не использую функцию cLog, но я могу использовать logDebug везде, и я могу создавать аналогичные версии с разными предварительными заливками:

Вы видите, как мы превратили нашу исходную функцию log, которая могла делать все, но требовала 4 параметра, в несколько отдельных функций, которые делают одну вещь, но требуют меньше параметров. Дело в том, что будет намного проще писать и намного чище читать что-то вроде errorMsg(errorObj), чем log("ERROR", Date(), "There is an error here", errorObj), не так ли?

Каррирование в этом случае дает несколько преимуществ:

  1. Это помогает нам сделать наш код более читабельным.
  2. Он очищает код, абстрагируя некоторые из менее важных и повторяемых значений.
  3. Это помогает нам повторно использовать наш код несколькими способами.

Фильтрация

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

filter(data, 'first_name', 'John', 'eq')

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

Посмотрите на следующий пример:

Функция filter — это грубая оболочка метода filter, так что нам не нужно каждый раз беспокоиться о создании этой лямбда-функции. Вместо этого это абстрагирует нас от него, и мы можем просто беспокоиться о наших параметрах фильтрации. Да, есть способы сделать это лучше, но суть не в этом, дело вот-вот наступит.

С помощью каррирования мы можем создавать частичные версии filter для захвата общих параметров фильтрации, которые нам могут понадобиться. Такие вещи, как фильтрация по возрасту или по имени, чтобы мы могли упростить наш синтаксис, как мы делали раньше в примере с ведением журнала.

В начале приведенного выше фрагмента мы определили набор предустановленных фильтров. Затем мы можем увидеть результаты в конце: гораздо более читаемый сценарий и гораздо более гибкий.

Вывод из этого примера, чтобы было понятно, таков:

//olderThan30
[
  {
    first_name: 'Fernando',
    last_name: 'Doglio',
    age: 38,
    city: 'Madrid'
  },
  { first_name: 'John', last_name: 'Doe', age: 40, city: 'NY' },
  {
    first_name: 'Optimus',
    last_name: 'Prime',
    age: 100000,
    city: 'Cybertron'
  }
]
----
//olderThanTime
[
  {
    first_name: 'Optimus',
    last_name: 'Prime',
    age: 100000,
    city: 'Cybertron'
  }
]
----
//me
[
  {
    first_name: 'Fernando',
    last_name: 'Doglio',
    age: 38,
    city: 'Madrid'
  }
]

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

Давайте теперь посмотрим на лучшую часть: реализацию функции curry.

Реализация

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

Как я показал вам, функция curry должна возвращать последовательность вызываемых функций, каждая из которых принимает один параметр, в зависимости от количества параметров исходной функции. Мы можем сделать это с помощью небольшой магии рекурсии:

Что делает приведенный выше код:

  1. Он возвращает новую функцию с именем curried, которая начнет захват параметров внутри массива params.
  2. Если не вызывается с точным количеством атрибутов, которое получает исходная функция, она будет считать, что это частичный вызов, поэтому вернет новую функцию.
  3. Новая функция получает другой параметр, вызывает исходный и объединяет оба списка параметров вместе. Таким образом собирая все индивидуальные параметры.
  4. Теперь возвращаемся к пункту 2, пока, конечно, не собрали все необходимые параметры, а затем вызываем исходную функцию с помощью метода apply (что позволяет передать все атрибуты в виде массива).

Это работает, вы можете проверить это на примерах, приведенных до сих пор. Но если мы вернемся к примерам, особенно к фильтрам, создается впечатление, что мы не доводим нашу логику карри до максимума.

Было бы очень здорово, если бы мы могли сделать что-то вроде этого:

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

Мы можем разрешить использование 1 подстановочного знака со следующей логикой:

Теперь, если используется подстановочный знак, когда приходит время вызывать исходную функцию, мы возвращаем еще одну функцию, обертывающую ее. Эта новая функция будет запрашивать фактическое значение подстановочного знака перед выполнением последнего вызова.

Таким образом, мы получаем еще большую гибкость, и если у вас есть время и энергия, вы также можете добавить поддержку переменного количества подстановочных знаков, которые будут использоваться в цепочке каррирования. Я оставлю это вам!

Теперь каррирование стало более понятным? Это имеет больше смысла?

Заключение

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

У вас остались вопросы? Оставляйте их в комментариях, и я постараюсь помочь!

Создавайте компонуемые интерфейс и серверную часть

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых фреймворках, таких как React или Node. Создавайте масштабируемые и модульные приложения с мощными и приятными возможностями разработки.

Перенесите свою команду в Bit Cloud, чтобы совместно размещать и совместно работать над компонентами, а также значительно ускорить, масштабировать и стандартизировать разработку в команде. Начните с компонуемых интерфейсов, таких как Design System или Micro Frontends, или исследуйте компонуемый сервер. Попробуйте →

Узнать больше