Каррирование в JS | Кодементор
Привет, мир!
JavaScript — это мультипарадигменный язык программирования, что означает, что он поддерживает как процедурное, так и функциональное программирование. Функциональное программирование позволяет делать забавные, почти волшебные вещи. Показательный пример: карри. В этом посте мы попытаемся понять, что такое каррирование и как его реализовать простым способом.
Функции высшего порядка
Чтобы реализовать каррирование, нам нужно узнать о функциях более высокого порядка. Так что же такое ХОФ?
Когда мы говорим о функциях высшего порядка, мы имеем в виду функцию, которая либо принимает одну или несколько функций в качестве аргументов, либо возвращает функцию в качестве результата.
Вы найдете только функции более высокого порядка в языках программирования, которые поддерживают первоклассные функции (или рассматривают функции как первоклассные граждане). Это означает язык, который позволяет нам хранить функции в переменных, передавать их как параметры в вызовах функций или возвращать их в результате вызова другой функции.
Вау! Звучит сложно. Ну, это не так. Потому что, скорее всего, вы уже используете HOF в своем коде. Помните карту, фильтр и уменьшение? Это функции высшего порядка.
Давайте создадим простую функцию высшего порядка.
const doubleFunc = fn => {
return (...args) => {
return 2 * fn(...args);
};
};
Так что здесь происходит?
Мы написали функцию под названием doubleFunc
который принимает функцию fn
в качестве аргумента и возвращает новую функцию, которая принимает произвольное количество аргументов. После того, как мы предоставим это произвольное количество аргументов этой функции, она затем применяется fn
к ним, умножает результат на 2 и возвращает результат.
Мы собрали произвольное количество аргументов в массив args выше, используя оператор расширения/остатка (…) в ES6. Подробнее об этом читайте в потрясающей книге Кайла Симпсона. здесь.
const add = (x, y) => x + y;
const doubleAdd = doubleFunc(add);
console.log(doubleAdd(1, 4)); //=> 10
Прохладный? Прохладный.
карри
Теперь, когда мы понимаем, что такое функция высшего порядка, как мы можем использовать это знание для создания каррирующей утилиты? Давайте сначала определим каррирование.
Каррирование — это процесс получения функции с несколькими аргументами и возврата ряда функций, которые принимают один аргумент и в конечном итоге разрешаются в значение.
Ладно, это немного сложно уложить в голове. Проще говоря, каррирование — это процесс преобразования функции, которая принимает несколько аргументов, в функцию, которая принимает меньше аргументов, путем исправления некоторых аргументов.
Давайте напишем пару инкрементных функций, изменив add
.
const add = x => y => x + y;
const inc1 = add(1);
const inc2 = add(2);
inc1(4); //=> 5
inc2(4); //=> 6
Большой! Каррируя наши add
мы сделали ее логику многоразовой и вытащили из нее 2 функции.
Это хорошо и все такое, но как мы можем каррировать любую функцию? Напишем для этого утилиту.
function curry(fn) {
return function(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return function(...more) {
return curry(fn)(...args, ...more);
};
};
}
Ладно, давай разберёмся. Мы написали функцию под названием curry
который принимает функцию fn
в качестве аргумента и возвращает функцию n-арности (функцию, которая принимает произвольное количество аргументов). Эта функция делает несколько вещей.
- Если количество аргументов, предоставленных этой функции, равно или больше, чем количество аргументов, необходимых функции
fn
он просто вызываетfn
с этими аргументами. - В противном случае он возвращает другую функцию n-арности, которая при предоставлении некоторого
more
аргументы, пытается вызвать каррированныйfn
сargs
а такжеmore
.
Этот процесс продолжается до тех пор, пока не будет выполнено условие шага 1.
Если вы не понимаете, что здесь происходит, я рекомендую вам скопировать эту утилиту, добавить к ней регистраторы и использовать ее для создания собственной простой функции.
Приведенный выше фрагмент слишком многословен. Давайте перепишем его с помощью стрелочных функций.
const curry = fn => (...args) =>
args.length >= fn.length ?
fn(...args) :
(...more) => curry(fn)(...args, ...more);
Гораздо круче! Давайте воспользуемся этой вновь обретенной силой.
const sort = (ascend, list) => {
const ascCmp = (a, b) => a > b;
const dscCmp = (a, b) => a < b;
return [...list].sort(ascend ? ascCmp : dscCmp);
};
const curriedSort = curry(sort);
const sortAsc = curriedSort(true);
const sortDsc = curriedSort(false);
const data = [9, 99, 29, 42, 45, 57, 13];
sortAsc(data); //=> [9, 13, 29, 42, 45, 57, 99]
sortDsc(data); //=> [99, 57, 45, 42, 29, 13, 9]
карри sort
мы могли частично применять функцию, зафиксировав значение ascend
флаг и сохранение полученной функции, которая теперь принимает только список чисел, в отдельную переменную.
Частичное применение функций может помочь нам повторно использовать наши функции более чистым способом. Мы можем даже передавать эти частично примененные функции в наше приложение для отложенной оценки по строке выполнения, когда нам будут доступны остальные аргументы.
Функции высшего порядка — это хлеб с маслом функционального программирования, а каррирование и частично применяемые функции позволяют использовать функции высшего порядка гораздо более эффективно и лаконично. Но HOF позволяют гораздо больше, чем просто каррирование, такие вещи, как композиция, запоминание и преобразование и т. д.
Если вам понравился этот вкус функционального программирования в JS, вы должны проверить эти библиотеки функционального программирования: В рамке а также Лодаш/fp.
Это все для карри, ребята. Надеюсь, вы узнали что-то об этой классной технике. Если вам понравился этот пост, пожалуйста, похлопайте и поделитесь. Чао!