Нет, термин «каррирование» никак не связан со знаменитой приправой и вообще не имеет никакого отношения к восточной кухне. Это приём в функциональном программировании, позволяющий преобразовать функцию, заменив её несколько первых аргументов константными значениями, тем самым создав новую функцию с меньшим количеством аргументов на основе старой. Этот будет удобно применять в случае, когда первые несколько аргументов функции заранее известны, и указывать их при каждом вызове нет необходимости. Для краткости будем называть преобразовываемую функцию каррируемой, а функцию, которая её преобразовывает, каррирующей.
Часто под каррированием понимают замену на константное значение лишь одного аргумента каррируемой функции. Я предлагаю же рассмотреть более общий случай, когда заменяемых аргументов каррируемой функции несколько.
Реализация
Для реализации механизма каррирования нам необходимо написать каррирующую функцию, которая принимает в качестве аргументов каррируемую функцию и значения, на которые необходимо заменить первые аргументы каррируемой функции.
Давайте сначала рассмотрим более простой вариант, когда каррируемая функция и количество аргументов, от которых нужно избавиться, известно заранее. В качестве каррируемой функции, для примера, возьмём что-то простое, например, функцию, складывающую два числа:
function sum(a, b) { return a + b; }
После того, как эта функция будет подвержена каррированию с заменой одного аргумента на некоторое значение, можно будет получить функцию, которая, например: инкрементирует (увеличивают на единицу), декрементирует (уменьшают на единицу) или прибавляют к своему уже единственному аргументу заданное заранее значение.
Делается это относительно просто:
function curry(a) { return function (b) { // в этом вызове аргумент a заменён на переданное в функцию curry значение return sum(a, b); }; } var inc = curry(1); console.log(inc(5)); // 6 var dec = curry(-1); console.log(dec(3)); // 2 var plusFive = curry(5); console.log(plusFive(5)); // 10
Если передавать каррируемую функцию с известным количеством аргументов в качестве аргумента каррирующей, но по-прежнему оставить количество заменяемых аргументов каррируемой функции заданным, то задача усложниться ненамного.
Рассмотрим ещё одну простую функцию:
function sayTwoWords(word1, word2) { console.log(word1 + " " + word2); }
Немного изменим каррирующую функцию, добавив в качестве параметра каррируемую ей функцию.
function curry(func, a) { return function (b) { return func(a, b); }; }
Теперь функции curry можно передавать каррируемую функцию в качестве аргумента.
var sayHelloTo = curry(sayTwoWords, "Hello"); sayHelloTo("Bob"); sayHelloTo("Marry"); var sayGoodbyeTo = curry(sayTwoWords, "Goodbye"); sayGoodbyeTo("Bob"); sayGoodbyeTo("Marry"); var minusThree = curry(sum, -3); console.log(minusThree(13)); // 10
Теперь рассмотрим более сложный вариант, когда каррируемая функция заранее неизвестна и неизвестно количество аргументов, которые необходимо заменить на константные значения.
Вспомним, что в js функция при вызове может принимать произвольное количество аргументов, вне зависимости от того, сколько их было указанно при объявлении. Пользуясь этой возможностью, объявим каррирующею функцию с одним параметром func, в котором будет передаваться каррируемая функция. Остальные же параметры будут значениями, на которые будут заменяться первые аргументы каррируемой функции. Эти значения мы будем извлекать из объекта arguments, в котором содержаться все значения аргументов функции, и записывать в массив.
function curry(func) { var args = arguments, curryArgs = []; /* * Записываем значения, на которые * заменим первые аргументы * каррируемой функции в массив. */ for (var i = 1; i < args.length; i++) { curryArgs[i - 1] = args[i]; } // преобразуем функцию и возвращаем её }
В возвращаемой функции нам необходимо вызвать каррируемую функцию, подставив в качестве первых аргументов значения из массива curryArgs, а в качестве остальных — значения, переданные в качестве параметров возвращаемой функции. Для этого воспользуемся нативным методом apply, который позволяет вызвать функцию со значениями аргументов, которые переданы ему в массиве. Этот массив метод apply принимает в качестве второго параметра. В качестве первого он принимает объект, который будет возвращаться при обращении к this внутри функции. Перед тем, как вызвать метод apply от каррируемой функции, преобразуем в массив объект arguments возвращаемой функции и склеим, при помощи метода concat, массив curryArgs c полученным массивом. Результат этой операции и передадим в качестве второго аргумента методу apply. В итоге функция curry примет вид:
function curry(func) { var args = arguments, curryArgs = []; for (var i = 1; i < args.length; i++) { curryArgs[i - 1] = args[i]; } return function () { // convert arguments to array var argsArr = Array.prototype.slice.call(arguments, 0); curryArgs = curryArgs.concat(argsArr); return func.apply(this, curryArgs); } }
В качестве завершающего штриха можно проверять является ли первый аргумент функции curry функцией.
function curry(func) { var args = arguments, curryArgs = []; if (typeof func !== 'function') { throw new Error('The first arguments must be function!'); } for (var i = 1; i < args.length; i++) { curryArgs[i - 1] = args[i]; } return function () { // convert arguments to array var argsArr = Array.prototype.slice.call(arguments, 0); curryArgs = curryArgs.concat(argsArr); return func.apply(this, curryArgs); } }
Теперь приведу пару практических примеров. Пусть у нас есть функция, создающая html-элемент с заданными свойствами. Первым аргументом она принимает имя html-элемента, а вторым объект, содержащий имена и значения свойств.
function createElement(tagName, properties) { var el = document.createElement(tagName); for (var prop in properties) { el[prop] = properties[prop]; } return el; }
Теперь создадим функцию, которая создаёт div c заданными свойствами.
var createDiv = curry(createElement, 'DIV');
Возможно, вам покажется, что проще было бы сделать вот так:
var createDiv = function (properties) { return createElement('DIV', properties); }
Однако этот синтаксис длиннее и нам пришлось указывать второй аргумент каррируемой функции, что кажется мелочью, но всё же излишне. А теперь представьте, что у каррируемой функции список аргументов достаточно длинный или он вовсе переменной длинны. И, хотя каррирование, работает медленнее, оно прельщает своей краткостью и простотой использования.
Теперь рассмотрим предельный случай, когда у каррируемой функции все аргументы заменяются константными значениями.
var createMessageBox = curry(createElement, 'DIV', {className: 'message-box'});
Функция createMessageBox будет создавать div c css-классом message-box и не будет принимать никаких параметров.
На этом всё, что я могу вам рассказать про каррирование. Как всегда, желаю вам успехов!