javascript currying
Asked Answered
W

4

8

I'm trying to create curry function that can be applied to any function and return another, with 1 of the arguments applied. Properties that I want to have:

  1. If function has only one argument curry function should return value: f(a); curry(f,x) = f(x);
  2. If function has many arguments currey should retrun curried function: g(a1,a2,..,aN); curry(g,x) = g2(a2,..,aN) : g2(a2,..aN)=g(x,a2,...,aN)
  3. Length propery of curried function should work "as needed" g.length = N => curry(g,x).length = N-1

There is some implementations of curry in Prototype Framework and discussion in one blog. But this implementation is not good because it doesn't work well on functions with only one argument (1), and also returning function 'length' attribute is 0 (3).

For first property there is an easy implementation:

 function curry(f,x) {
    if (f.length == 1) return f(x);
    ...
 }

But I don't know how to work with 3rd rule, i.e. function can be constucted as inner function since there will be a nested Lexical Environment and will be able to use f:

function curry(f,x) {
   return function() { ... }
}

but in this case I'll no longer will able to explicitly set parameters. On the other hand function can be constructed with 'new Function' statement, smth like that:

 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+")");
 }

But in this situation f and x will unbound because anonymous function will be created in Global Lexical Environment.

So the questions:

  1. is there a way to explicitly set parameters count when creating function with function keyword?
  2. is there a way to set Environment of function created with 'new Function' statement?
  3. us there a way to solve my problem in any other way?
Welcy answered 11/3, 2011 at 13:22 Comment(3)
Just because a function is declared with a single argument doesn't mean that it doesn't accept more.Envious
I find the first requirement asymmetrical. Currying a function of one argument should return a function with no arguments in my opinion...Maryalice
I know that if function declared with some arguments it can accept more, but anyway there can be a situation when I want to check length attribute and solution will break this logic. There was no answer for 1 and 2 question but the core problem is solved so I mark question as solvedWelcy
E
6

The way that the Functional library implements it is to take the parameters passed in to "curry()" as the first parameters to be passed. The function result of the "curry" operation will then take any additional parameters passed in when it is invoked and add them at the end of the argument list. It doesn't worry at all about argument list length, because that's not a fixed thing in JavaScript generally so there's really no point.

Thus:

var curry = myFunction.curry("Tuesday", x + y);

So calling:

curry(100, true);

will be just like calling:

myFunction("Tuesday", x + y, 100, true);

Functional has another function called "partial()" that allows a more controlled substitution of parameters. When you call "partial()", you pass in a dummy argument ("_") to indicate where "holes" are in the argument list:

var partialFunc = myFunction.partial("Tuesday", _, 100, true, _, "banana");

Those two "_" parameters mean that the resulting "partialFunc" should drop first two arguments passed to it into those slots in the argument list:

partialFunc(x + y, "Texas");

is thus just like calling:

myFunction("Tuesday", x + y, 100, true, "Texas", "banana");

I heartily recommend getting that library and looking at the code involved. It's surprisingly succinct and clear.

One more thing: it's important to note that because JavaScript is not a lazy-evaluation language, this isn't really the same as the "curry" operation in a lazy functional language like Haskell. The distinction is that the arguments at "curry time" are evaluated and therefore sort-of "cooked" into the result. In a lazy language, things are different.

Envious answered 11/3, 2011 at 13:34 Comment(0)
E
3
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!"));
Erland answered 11/3, 2011 at 13:39 Comment(0)
E
2

For years now I have used a function prototype for curry that looks like this:

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)));
    };
};

Maybe it will suit your needs as well.

You simply use it like so:

function fn1(arg1,arg2) { /*...*/ }

var fn1Curried = fn1.curry('whatever'); //sets arg1

It will work with any number of arguments.

Encephalo answered 11/3, 2011 at 14:5 Comment(0)
B
0
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);
}
Backslide answered 26/12, 2012 at 20:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.