Предыдущие статьи: Часть 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 Goldberg:»Javascript and Functional Programming: Currying (Pt.4)«