Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript curry

Tags:

javascript

I`m a newbie at JavaScript trying to understand this tutorial about currying from Oreilly JavaScript Cookbook.

Could someone be kind enough to explain this program in detail step by step in plain language. Please make sure to explain the "null" argument passed in the second last line of the program. Thank you in advance if you can help.

function curry(fn, scope) {
    scope = scope || window;
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }
    return function() {
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args2.push(arguments[i]);
        }
        var argstotal = args.concat(args2);
        return fn.apply(scope, argstotal);
    };
}

function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

var diffOrigin = curry(diffPoint, null, 3.0, 4.0);
var newPt = diffOrigin(6.42, 8.0); //produces array with 3
like image 310
mjmitche Avatar asked Mar 03 '11 03:03

mjmitche


2 Answers

If you dont mind a suggestion, start with Javascript: The Good Parts. Follow that up with either Javascript Patterns, or Secrets of the Javascript Ninja for more advanced techniques. Cookbooks are more for canned solutions to problems then a learning resource.

Matt Ball did a good job explaining whats going on. If you are a beginner, I wouldn't sweat trying to figure out curry functions anyways. That aside, IMO this curry function is terrible. This is how I would change it

// this is doing binding and partial function application, 
// so I thought bind was a more appropriate name
// The goal is that when you execute the returned wrapped version of fn, its this will be scope
function bind(fn, scope) {
  // arguments is an implicit variable in every function that contains a full list
  // of what was passed in. It is important to note that javascript doesn't enforce arity.
  // since arguments is not a true array, we need to make it one.
  // a handy trick for this is to use the slice function from array,
  // since it will take arguments, and return a real array.
  // we are storing it in a variable, because we will need to use it again.
  var slice =  Array.prototype.slice,
      // use slice to get an array of all additional arguments after the first two
      // that have been passed to this function.
      args = slice.call(arguments, 2);

  // we are returning a function mostly as a way to delay the execution.
  // as an aside, that this is possible in a mainstream language is a minor miracle
  // and a big part of why i love javascript.
  return function() {
    // since functions are objects in javascript, they can actually have methods.
    // this is one of the built in ones, that lets you execute a function in a different
    // context, meaning that the this variable inside the 
    // function will actually refer to the first argument we pass in.

    // the second argument we are jamming together the arguments from the first function
    // with the arguments passed in to this wrapper function, and passing it on to fn.
    // this lets us partially apply some arguments to fn when we call bind.
    return fn.apply(scope, args.concat(slice.call(arguments)));
  }
}

JavaScript, while wonderful, is horribly verbose. Needlessly repeating var while defining your bindings just adds a lot of noise. Also, there is no need to painfully build a real array like that, slice will take arguments and give you a real array back. Especially in this case where we are using it twice, AND we actually want to slice out the first two args anyways. Finally, when you apply and your first arg is null, JavaScript will apply the global object for you. There is no need to do that explicitly.

IMO my 5 line function body kicks the crap out of o'reillys 11 lines, and IMO it is much more readable.

like image 66
Matt Briggs Avatar answered Sep 25 '22 12:09

Matt Briggs


// define the curry() function
function curry(fn, scope) {

    // set the scope to window (the default global object) if no scope was passed in.
    scope = scope || window;

    // Convert arguments into a plain array, because it is sadly not one.
    // args will have all extra arguments in it, not including the first 2 (fn, scope)
    // The loop skips fn and scope by starting at the index 2 with i = 2
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }

    // Create the new function to return
    return function() {

        // Convert any arguments passed to the this function into an array.
        // This time we want them all
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        // Here we combine any args originally passed to curry, with the args
        // passed directly to this function.
        //   curry(fn, scope, a, b)(c, d)
        // would set argstotal = [a, b, c, d]
        var argstotal = args.concat(args2);

        // execute the original function being curried in the context of "scope"
        // but with our combined array of arguments
        return fn.apply(scope, argstotal);
    };
}

// Create a function to be curried
function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

// Create a curried version of the diffPoint() function
//   arg1: the function to curry
//   arg2: the scope (passing a falsy value causes the curry function to use window instead)
//   arg3: first argument of diffPoint() to bake in (x1)
//   arg4: second argument of diffPoint() to bake in (y1)
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);

// Call the curried function
// Since the first 2 args where already filled in with the curry, we supply x2 and y2 only
var newPt = diffOrigin(6.42, 8.0);

In this case the scope argument isn't used at all. scope sets what the this object is. The function you are currying doesn't use this so it has no real effect. The scope is set when fn.apply(scope, args) is called, which both sets the scope to run in and provides arguments to pass in.

like image 35
Alex Wayne Avatar answered Sep 24 '22 12:09

Alex Wayne