Compare curry functions

JavaScript performance comparison

Revision 4 of this test case created by Joshua Newman

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    var add = function(f, s, t, fo) {
      return f + s + t + fo;
    };
};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
npm curry
var curry = (function() {
  var slice = Array.prototype.slice;
  var toArray = function(a) {
    return slice.call(a)
  }

  // fn, [value] -> fn
  //-- create a curried function, incorporating any number of
  //-- pre-existing arguments (e.g. if you're further currying a function).
  var createFn = function(fn, args) {
    var arity = fn.length - args.length;

    if (arity === 0) return function() {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 1) return function(a) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 2) return function(a, b) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 3) return function(a, b, c) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 4) return function(a, b, c, d) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 5) return function(a, b, c, d, e) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 6) return function(a, b, c, d, e, f) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 7) return function(a, b, c, d, e, f, g) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 8) return function(a, b, c, d, e, f, g, h) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 9) return function(a, b, c, d, e, f, g, h, i) {
      return processInvocation(fn, argify(args, arguments))
    };
    if (arity === 10) return function(a, b, c, d, e, f, g, h, i, j) {
      return processInvocation(fn, argify(args, arguments))
    };
    return createEvalFn(fn, args, arity);
  }

  // [value], arguments -> [value]
  //-- concat new arguments onto old arguments array
  var argify = function(args1, args2) {
    return args1.concat(toArray(args2));
  }

  // fn, [value], int -> fn
  //-- create a function of the correct arity by the use of eval,
  //-- so that curry can handle functions of any arity
  var createEvalFn = function(fn, args, arity) {
    var argList = makeArgList(arity);

    //-- hack for IE's faulty eval parsing -- http://stackoverflow.com/a/6807726
    var fnStr = 'false||' +
      'function curriedFn(' + argList + '){ return processInvocation(fn, argify(args, arguments)); }';
    return eval(fnStr);
  }

  var makeArgList = function(len) {
    var a = [];
    for (var i = 0; i < len; i += 1) a.push('a' + i.toString());
    return a.join(',');
  }

  // fn, [value] -> value
  //-- handle a function being invoked.
  //-- if the arg list is long enough, the function will be called
  //-- otherwise, a new curried version is created.
  var processInvocation = function(fn, args) {
    if (args.length > fn.length) return fn.apply(null, args.slice(0, fn.length));
    if (args.length === fn.length) return fn.apply(null, args);
    return createFn(fn, args);
  }

  // fn -> fn
  //-- curries a function! <3
  var curry = function(fn) {
    return createFn(fn, []);
  };
  return curry;
})();

curry(add)(1)(2)(3)(4);
pending…
Dispatch based curry
var curry = (function() {
  var slice = Array.prototype.slice;
  var RIGHT = 'right';

  var makeCurry = function(adder) {
    var reverse = adder === RIGHT;

    /**
     * Make a function that returns another function until it has all its arguments. Note that
     * the function === the return value until it's called w/ the specified arguments.
     *
     * @param {function} fn
     * @param {number} [len=fn.length] Forces the collected arguments to a specific value.
     * @param {boolean} [forceOne=false] Whether to prevent more than one arg at a time.
     * @param {boolean} [seal=true] Whether to prevent args beyond the len.
     * @param {Array.<*>} [held=[]] Arguments fro a previous call.
     * @return {function} A function that executes fn once it's been passed all its arguments.
     */

    return function currier(fn, len, forceOne, seal, held) {
      len = !! len ? len : fn.length;
      forceOne = !! forceOne;
      seal = seal !== false;
      held = held || [];
      var position = held.length;
      var captured = function curried(args) {
        var oldPosition = position;
        var nextHeld = held.slice();
        if (forceOne) {
          nextHeld[position++] = args;
        } else {
          nextHeld = nextHeld.concat(slice.call(arguments));
        }

        var next = null;
        var nextLen = nextHeld.length;
        if (nextLen < len) {
          next = currier(fn, len, forceOne, seal, nextHeld);
          position = oldPosition;
          return next;
        } else {
          if (seal) {
            // Clear off the beginning if it's a right
            if (reverse) {
              nextHeld.reverse().splice(0, nextLen - len);
              return fn.apply(this, nextHeld);
            }
            // Clear extras.
            else {
              return fn.apply(this, nextHeld.slice(0, len));
            }
          } else {
            return fn.apply(this, reverse ? nextHeld.reverse() : nextHeld);
          }
        }
      };
      captured._fn = fn;
      return captured;
    };
  };

  return makeCurry('push');
})();
curry(add)(1)(2)(3)(4);
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment