Javascript карри

Я пытаюсь создать функцию карри, которая может быть применена к любой функции и вернуть другую, с одним из примененных аргументов. Свойства, которые я хочу иметь:

  1. Если функция имеет только один аргумент, функция карри должна возвращать значение: f(a); карри (f,x) = f(x);
  2. Если функция имеет много аргументов, currey должен повторно запустить функцию с карри: g(a1,a2,..,aN); карри (g,x) = g2(a2,..,aN): g2(a2,..aN)=g(x,a2,...,aN)
  3. Значение длины функции карри должно работать "по мере необходимости" 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 будет создана анонимная функция.

Итак, вопросы:

  1. Есть ли способ явно установить количество параметров при создании функции с ключевым словом функции?
  2. Есть ли способ установить окружение функции, созданной с помощью оператора "новая функция"?
  3. Есть ли способ решить мою проблему любым другим способом?

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);
}
Другие вопросы по тегам