Javascript карри
Я пытаюсь создать функцию карри, которая может быть применена к любой функции и вернуть другую, с одним из примененных аргументов. Свойства, которые я хочу иметь:
- Если функция имеет только один аргумент, функция карри должна возвращать значение: f(a); карри (f,x) = f(x);
- Если функция имеет много аргументов, currey должен повторно запустить функцию с карри: g(a1,a2,..,aN); карри (g,x) = g2(a2,..,aN): g2(a2,..aN)=g(x,a2,...,aN)
- Значение длины функции карри должно работать "по мере необходимости" g.length = N => curry(g,x).length = N-1
Существует несколько реализаций карри в Prototype Framework и обсуждение в одном блоге. Но эта реализация не очень хороша, потому что она плохо работает с функциями только с одним аргументом (1), а также возвращает атрибут функции length с длиной 0 (3).
Для первого свойства есть простая реализация:
function curry(f,x) {
if (f.length == 1) return f(x);
...
}
Но я не знаю, как работать с третьим правилом, то есть функция может быть построена как внутренняя функция, так как будет вложенная лексическая среда и она сможет использовать f:
function curry(f,x) {
return function() { ... }
}
но в этом случае я больше не смогу явно устанавливать параметры. С другой стороны, функция может быть построена с помощью оператора 'new Function', например:
function curry(f,x) {
var args = [];
for (var i=1; i<f.length; i++) {
args.push('a'+i);
}
var sa = args.join();
return new Function(sa,"return f(x,"+sa+")");
}
Но в этой ситуации f и x не будут связаны, потому что в Global Lexical Environment будет создана анонимная функция.
Итак, вопросы:
- Есть ли способ явно установить количество параметров при создании функции с ключевым словом функции?
- Есть ли способ установить окружение функции, созданной с помощью оператора "новая функция"?
- Есть ли способ решить мою проблему любым другим способом?
4 ответа
Способ, которым функциональная библиотека реализует это, состоит в том, чтобы взять параметры, переданные в "curry()", в качестве первых передаваемых параметров. Результат функции операции "карри" будет затем принимать любые дополнительные параметры, переданные при ее вызове, и добавлять их в конец списка аргументов. Он не беспокоится о длине списка аргументов, потому что в JavaScript это не является чем-то постоянным, поэтому в этом нет никакого смысла.
Таким образом:
var curry = myFunction.curry("Tuesday", x + y);
Так зовем:
curry(100, true);
будет просто как звонить:
myFunction("Tuesday", x + y, 100, true);
У функционала есть еще одна функция, называемая "частичная ()", которая обеспечивает более контролируемую подстановку параметров. Когда вы вызываете "partal()", вы передаете фиктивный аргумент ("_"), чтобы указать, где" дыры "находятся в списке аргументов:
var partialFunc = myFunction.partial("Tuesday", _, 100, true, _, "banana");
Эти два параметра "_" означают, что результирующий "absoluteFunc" должен отбросить первые два передаваемых ему аргумента в эти слоты в списке аргументов:
partialFunc(x + y, "Texas");
таким образом, это как вызов:
myFunction("Tuesday", x + y, 100, true, "Texas", "banana");
Я искренне рекомендую получить эту библиотеку и посмотреть на код. Это удивительно сжато и ясно.
Еще одна вещь: важно отметить, что, поскольку JavaScript не является языком ленивых вычислений, на самом деле это не то же самое, что операция "карри" в ленивом функциональном языке, таком как Haskell. Различие состоит в том, что аргументы в "время карри" оцениваются и, следовательно, как бы "готовятся" к результату. На ленивом языке все по-другому.
function curry(fn, args) {
// no need to var these, they are scoped via argument list - we overwrite them
// convert the arguments to a real array:
args = [].slice.apply(arguments);
// first argument is a function:
fn = args.shift();
return function() {
// get internal args
var iArgs = [].slice.apply(arguments);
// apply curried arguments, then our arguments:
return fn.apply(this, args.concat(iArgs));
}
}
function add(a,b) { return a+b; }
var add2 = curry(add, 2);
alert(add2(5)); //7
var hello = curry(add, "Hello ");
alert(hello("World!"));
В течение многих лет я использовал прототип функции для карри, который выглядит следующим образом:
Function.prototype.curry = function curry() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function curryed() {
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};
};
Может быть, это будет соответствовать вашим потребностям.
Вы просто используете это так:
function fn1(arg1,arg2) { /*...*/ }
var fn1Curried = fn1.curry('whatever'); //sets arg1
Это будет работать с любым количеством аргументов.
function curry(func) {
var initial_args = [].slice.apply(arguments, [1]);
var func_args_length = func.length;
function curried(args) {
if (args.length >= func_args_length) {
return func.apply(null, args);
}
return function () {
return curried(args.concat([].slice.apply(arguments)));
};
}
return curried(initial_args);
}