javascript

Предыдущие статьи: Часть 1, Часть 2, Часть 3

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

const magicPhrase = 
 (magicWord) => 
  (muggleWord) => 
    magicWord + muggleWord

Таким образом, данную функцию можно вызвать следующим синтаксисом:

Волшебствооо

Написать функцию, которая будет возвращать какие-то выходные значения (возможно, другую функцию!), не так уж и просто. К счастью, существуют функциональные библиотеки-помощники по JS (Ramda, LoDash) со всяческими служебными методами, в т.ч. и с каррированием. Утилита curry находит функции с обычным объявлением и преобразует их в несколько функций с одним аргументом. Таким образом, из предыдущего кода можно сделать следующее:

import _ from "lodash"
const magicPhrase = _.curry((magicWord, muggleWord) => magicWord + muggleWord)
const muggleWordAccepter = magicPhrase("Абра кадабра")
muggleWordAccepter("швабра")

Другой пример – это обновленная реализация нашей любимой функции add:

import _ from "lodash"
const addFunction = _.curry((a, b) => a + b)
const addOne = add(1)
addTen(1)

В первой переменной мы как бы «пред-загружаем» функцию add. И благодаря JS-замыканию наша функция может запоминать первое переданное ей значение.

Зачем нужно каррирование

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

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

3.  Каррирование позволяет преобразовать все функции с одним элементом к функциям с поддержкой массивов (списки). Делается это путем оборачивания функции в map.

const getObjectId = (obj) => obj.id // работает с одним объектом
const arrayOfObjects = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]
const arrayOfIDs = arrayOfObjects.map(getObjectId)
Бам! Наша функция с одним элементом стала работать с массивом!

Примеры

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

const getFirstTwoLettersOfWord = (word) => word.substring(0,2)
// Мы преобразуем эту строку, оборачивая ее в метод map
["aabb", "bbcc", "ccdd", "ddee"].map(getFirstTwoLettersOfWord)

 

Следующий пример зародился из чудесного Mostly Adequate Guide с небольшим рефакторингом ES6 🙂

Давайте преобразуем функцию max так, чтобы она перестала ссылаться на какие-либо аргументы.

arr = [2,4,6,8,9]
// ОСТАВЛЯЕМ:
const getMax = (x, y) => {
  return x >= y ? x : y;
};

// ДЕЛАЕМ РЕФАКТОРИНГ ВОТ ЭТОГО:
const max = (arr) => {
  return arr.reduce((acc, x) => {
    return getMax(acc, x);
  }, -Infinity);
};
const max = arr.reduce(getMax, -Infinity)

Далее оборачиваем нативный JS-метод slice, делая его каррированным и работоспособным.

import _ from "lodash"
const arr = ["барни", "фред", "дэйв"]
arr.slice(0, 2) // ["барни", "фред"]
const slice = _.curry((start, end, arr) => arr.slice(start, end));
const sliceWithSetIndexes = slice(0,2)

sliceWithSetIndexes(arr) // ["барни", "фред"]

Заключение

Мы рассмотрели несколько примеров использования каррирования JS-функций. Каррирование относится к процессам преобразования функций с множественной арностью (количеством аргументов) к той же самой функции с меньшей арностью. Для запоминания ранее используемых аргументов используется замыкание JS. Каррирование реализует переплетение функций для улучшения их общей работоспособности. Но самое главное в том, что благодаря каррированию можно без проблем осуществлять композицию функций!

Перевод статьи Omer GoldbergJavascript and Functional Programming: Currying (Pt.4)«