Прочтите эту статью на испанском языке.
Каррирование — это процесс декомпозиции функции с более чем одним параметром в цепочку функций, вызываемых с одинаковым количеством аргументов, но частично. Название происходит от его применения в математике.
// function add without curry function add(a, b, c) { return a + b + c; } console.log( add(1, 2, 3) ); // 6 // Curry example. // It calls the function three times, // one argument at time function curryingAdd(a) { return (b) => { return (c) => { return a + b + c; }; }; } console.log( curryingAdd(1)(2)(3) ); // 6
Но это решение слишком специфично. Если бы мы хотели повторить одну и ту же технику для разных случаев, нам пришлось бы генерировать новую функцию для каждого из них с правильным количеством аргументов и вызовов.
Альтернативы
Ангус Кролл делится подходом, основанным на библиотеке Prototype.js. Он добавляет метод curry в прототип функции, позволяя использовать его для всех функций.
Я внес некоторые изменения в код, чтобы добавить некоторые пояснения:
Function.prototype.curry = function() { // without arguments, it returns the original function (this) if (arguments.length < 1) return this; // _method is the "currified" function. The first time // it will be the add() function, added at the bottom // of this example. But later, it will be the // anonymous function returned in each execution. // You can add a log here to test it. var __method = this; // Array.prototype.slice.call transforms the arguments // into an array. It allows to use the concat method later. var args = Array.prototype.slice.call(arguments); // Each call with .curry() returns a function, wrapping // the previously returned one. This anonymus function // will be called when all the chain of executions ends, // and the program starts doing the inverse path. return function() { // all collects the arguments from each call, // concatenating them with those from the previous call var all = args.concat(Array.prototype.slice.call(arguments); // __method saves a reference to the previous context, // so it will be called with the previous args, // and completing all. var result = __method.apply( this, all ); // When all the functions are called, the last one is the // original ( add() for this case ), and it is called with // all the parameters. return result; } } // función sin curry var add = function(a, b, c, d, e) { return a + b + c + d + e; } var a = add.curry(1); // curry is part of Function prototype var b = a.curry(1); var c = b.curry(1); var d = c.curry(1); console.log( d(1) ); // 5 var one = add.curry(1, 1, 1); // you can send more than one arg! var two = one.curry(1); console.log( two(1) ); // 5
Прототип.js
С Prototype просто импортируйте библиотеку, и curry будет готов к использованию, как и в предыдущем примере.
<script src="/path/to/prototype.js" ></script> <script> var add = function(a, b, c, d, e) { return a + b + c + d + e; } var one = add.curry(1, 1, 1); var two = one.curry(1); console.log( two(1) ); // 5 </script>
Lodash.js
Lodash не изменяет прототип функции, и это заставляет нас создавать новую версию нашей функции, используя ее метод _.curry. Библиотека создает цепочку возвратов автоматически.
<script src="/path/to/lodash.js" ></script> <script> var add = function(a, b, c, d, e) { return a + b + c + d + e; } var addCurry = _.curry(add); var two = addCurry(1,1,1)(1); console.log( two(1) ); // 5 <script>
Когда это полезно?
Как мы узнали, исходная функция больше не является единственной точкой входа. Мы можем создавать настраиваемые функции, которые можно вызывать в разных точках нашей программы, вплоть до конечного результата. Простой пример:
function bye(a, b) => `Have a ${a}, ${b}.`; const night = bye.curry('good night'); // Have a good night, see you tomorrow console.log( night('see you tomorrow') ); // Have a good night, sweet dreams console.log( night('sweet dreams') );
Вопросы? @mpjme поделился очень хорошим введением в своей серии видеороликов о функциональном программировании на JavaScript.