Функциональное программирование - это стиль написания кода, в котором функции передают как аргументы (колбэки) и отдают тоже функции без сайд-эффектов (изменения состояния приложения).
Множество языков приняли такую стилистику в работе с кодом. Самые популярные из них это: JavaScript, Haskell, Clojure, Erlang и Scala.
Возможностью передавать и возвращать функции породила много других концепций:
Что такое каррирование?
Это процесс в функциональном программировании при котором мы можем трансформировать функцию с несколькими аргументами в упорядоченную последовательность вложенных друг в друга уже других функций. Она возвращает новую функцию, которая ожидает уже следующий аргумент.
Она будет отдавать новую функцию до тех пор, пока не закончится количество аргументов. Аргументы остаются доступными в области видимости и используются при запуске кода, когда завершающая функция в цепочке каррирования отдаётся и запускается.
Каррирование - это процесс превращения функции с множественной арностью в функцию с меньшей арностью.
Обратите внимание, что термин арность относится напрямую к количеству аргументов которое берет функция. Для примера,
function fn(a, b) { //... } function _fn(a, b, c) { //... }
У функции fn два аргумента (2х арная функция), а _fn имеет три аргумента (3х арная функция).
Так что каррирование трансформирует функцию с несколькими аргументами в последовательную серию связанных функций, каждая из которых берет один из нужных аргументов.
Давайте посмотрим на простой пример:
function multiply(a, b, c) { return a * b * c; }
Функция берёт три аргумента, умножает числа и отдаёт результат.
multiply(1,2,3); // 6
Посмотрите как мы вызвали функцию умножения с полным набором аргументов. Теперь давайте создадим каррированную версию этой функции и посмотрим, как бы мы могли вызвать ту же функцию, но уже через серию запросов:
function multiply(a) { return (b) => { return (c) => { return a * b * c; } } } console.log(multiply(1)(2)(3)); // 6
Мы превратили функцию multiply(1,2,3) в несколько вызовов других вызовов multiply(1)(2)(3).
Просто напросто одна простая функция была переделана в серию последовательных функций. Чтобы получить результат умножения трёх чисел 1, 2 и 3, числа передаются одно за другим, каждое число передаётся следующей функции для запуска.
Мы могли бы разделить multiply(1)(2)(3) на несколько функций, чтобы вы лучше поняли процесс:
let mul1 = multiply(1); let mul2 = mul1(2); let result = mul2(3); console.log(result); // 6
Давайте рассмотрим всё по последовательности. Мы передали 1 функции multiply:
let mul1 = multiply(1);
Она отдаёт функцию:
return (b) => { return (c) => { return a * b * c } }
Теперь mul1 это функция с аргументом b.
Далее мы берём функцию mul1, передавая ей 2:
let mul2 = mul1(2);
Mul1 отдаст третью функцию:
return (c) => { return a * b * c }
Отданная функция теперь хранится в переменной mul2.
По-сути mul2 будет:
mul2 = (c) => { return a * b * c }
Где mul2 вызывается с 3 как параметром,
const result = mul2(3);
Что делает вычисления вместе с параметрами, переданными ранее: a=1, b=2 и получаем 6.
В общем, суть каррирования в том, что мы берём функцию и извлекаем другую функцию, которая отдаёт уже специализированную функцию.
Каррирование создаёт вложенные функции в соответствии с числом аргументов в ней. Каждая функция тут получает свой аргумент. Если нет аргумента, то нет и каррирования.
А каррирование действительно полезно?
Для примера у вас есть магазин и вы хотите дать 10% скидку вашим любимым клиентам:
function discount(price, discount) { return price * discount }
Когда один из таких клиентов что-нибудь покупает на 500$:
const price = discount(500, 0.10); // $50 // $500 - $50 = $450
Тут вы понимаете, что этот процесс будет происходить ежедневно:
const price = discount(1500, 0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000, 0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50, 0.10); // $5 // $50 - $5 = $45 const price = discount(5000, 0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300, 0.10); // $30 // $300 - $30 = $270
Мы можем просто каррировать функцию скидки, чтобы нам не пришлось всегда добавлять 0.10.
function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1);
Теперь нам нужна только цена для расчета стоимости со скидкой:
tenPercentDiscount(500); // $50 // $500 - $50 = $450
И снова, так получается, что некоторые клиенты уж очень нам важны, важнее других. И мы хотим им дать скидку уже в 20%.
Тут мы будем использовать нашу каррированую функцию скидки:
const twentyPercentDiscount = discount(0.2);
Мы делаем новую функцию для наших очень важных клиентов, вызывая функцию каррирования со значением 0.2, что есть 20%.
Отдаваемая функция twentyPercentDiscount будет использоваться для расчета скидок у таких важных клиентов:
twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000