|
// Using _.curry for comparison |
|
var _ = require('lodash'); |
|
|
|
// Function to curry for testing |
|
function add(x, y, z) { |
|
return x + y + z; |
|
} |
|
|
|
var i, n = 1000000; |
|
|
|
console.log(n + ' iterations'); |
|
|
|
//----------------------------------------------------------------- |
|
// Uncurried control |
|
// Run twice for JIT warm-up |
|
//----------------------------------------------------------------- |
|
console.log('---------------------------------------------'); |
|
console.log('uncurried'); |
|
console.log('---------------------------------------------'); |
|
|
|
var r = new Array(n); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = add(1,2,3); |
|
} |
|
|
|
r = new Array(n); |
|
var start = Date.now(); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = add(1,2,3); |
|
} |
|
|
|
console.log(Date.now() - start + '\tadd(1,2,3)'); |
|
|
|
//----------------------------------------------------------------- |
|
// Compare four approaches |
|
//----------------------------------------------------------------- |
|
|
|
testCurry('unscriptable dynamic compile', curry3(add)); |
|
|
|
testCurry('briancavalier dynamic compile', curry1(add)); |
|
|
|
testCurry('briancavalier args.length dispatch', curry2(add)); |
|
|
|
testCurry('lodash', _.curry(add)); |
|
|
|
//----------------------------------------------------------------- |
|
// unscriptable dynamic compile |
|
// https://gist.github.com/unscriptable/bb9f62add400fa45af61 |
|
//----------------------------------------------------------------- |
|
function curry3 (f) { |
|
var arity = f.length; |
|
var params = []; |
|
var end = createEnd3(f, arity); |
|
return createCurried3(params, arity, end); |
|
} |
|
|
|
function createEnd3 (f, arity) { |
|
var src = 'return f('; |
|
for (var i = 0; i < arity; i++) { |
|
src += (i ? ', p[' : 'p[') + i + ']'; |
|
} |
|
src += ');'; |
|
var endCall = new Function ('f', 'p', src); |
|
return function end (p) { |
|
return endCall(f, p); |
|
}; |
|
} |
|
|
|
function createCurried3 (collected, arity, end) { |
|
return function () { |
|
var params = concat(collected, arguments); |
|
return params.length < arity |
|
? createCurried3(params, arity, end) |
|
: end(params); |
|
}; |
|
} |
|
|
|
//----------------------------------------------------------------- |
|
// briancavalier dynamic compile |
|
// https://gist.github.com/briancavalier/d2385e8fbca53c8d056b |
|
//----------------------------------------------------------------- |
|
function curry1 (f) { |
|
var arity = f.length; |
|
var params = []; |
|
var end = createEnd(f, arity); |
|
return createCurried(params, arity, end); |
|
} |
|
|
|
function createEnd (f, arity) { |
|
var src = 'return function ' + f.name + '_curried (args) { return f('; |
|
for (var i = 0; i < arity; i++) { |
|
src += (i ? ', args[' : 'args[') + i + ']'; |
|
} |
|
src += '); };'; |
|
return (new Function ('f', src)(f)); |
|
} |
|
|
|
function createCurried (collected, arity, end) { |
|
return function () { |
|
var params = concat(collected, arguments); |
|
return params.length < arity |
|
? createCurried(params, arity, end) |
|
: end(params); |
|
}; |
|
} |
|
|
|
//----------------------------------------------------------------- |
|
// briancavalier args.length dispatch |
|
//----------------------------------------------------------------- |
|
function curry2(f, arity) { |
|
var a = arguments.length > 1 ? arity : f.length; |
|
return a < 2 ? f : curryArity(f, a, []); |
|
} |
|
|
|
function curryArity(f, arity, args) { |
|
return function() { |
|
var accum = concat(args, arguments); |
|
|
|
return accum.length < arity |
|
? curryArity(f, arity, accum) |
|
: runCurried(f, accum, this); |
|
}; |
|
} |
|
|
|
function runCurried(f, args, thisArg) { |
|
switch(args.length) { |
|
// Currying a 0 or 1-arg function would be useless |
|
case 2: return f.call(thisArg, args[0], args[1]); |
|
case 3: return f.call(thisArg, args[0], args[1], args[2]); |
|
case 4: return f.call(thisArg, args[0], args[1], args[2], args[3]); |
|
case 5: return f.call(thisArg, args[0], args[1], args[2], args[3], args[4]); |
|
default: |
|
return f.apply(thisArg, args); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------- |
|
// fast array concat helper |
|
//----------------------------------------------------------------- |
|
function concat(a, b) { |
|
var al = a.length; |
|
var bl = b.length; |
|
var c = new Array(al + bl); |
|
var i; |
|
|
|
for(i=0; i<al; ++i) { |
|
c[i] = a[i]; |
|
} |
|
|
|
for(i=0; i<bl; ++i) { |
|
c[i+al] = b[i]; |
|
} |
|
|
|
return c; |
|
} |
|
|
|
//----------------------------------------------------------------- |
|
// test runner |
|
// Each test is repeated to account for JIT warm-up. Only the |
|
// second is timed. |
|
//----------------------------------------------------------------- |
|
function testCurry(name, f) { |
|
|
|
console.log('---------------------------------------------'); |
|
console.log(name); |
|
console.log('---------------------------------------------'); |
|
var r, i, start; |
|
|
|
// Test 1: No partial application |
|
// This test is important since it's desirable for currying to have |
|
// a minimal impact on "regular" function calls in JS. |
|
r = new Array(n); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1,2,3); |
|
} |
|
|
|
r = new Array(n); |
|
start = Date.now(); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1,2,3); |
|
} |
|
console.log(Date.now() - start + '\tadd(1,2,3)'); |
|
|
|
// Test 2: Partially apply all args except last |
|
// This is a primary use case for currying, ie to a produce a one-arg |
|
// function which is then called many times. |
|
var partiallyApplied = f(1,2); |
|
r = new Array(n); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = partiallyApplied(3); |
|
} |
|
|
|
r = new Array(n); |
|
start = Date.now(); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = partiallyApplied(3); |
|
} |
|
console.log(Date.now() - start + '\tadd(1,2) then many add(3)'); |
|
|
|
// Test 3: Individually apply each arg |
|
// You'll likely never do this, but it's a worthwhile perf test, esp |
|
// in comparison to the first test above (ie fully applying the function |
|
// in one go) |
|
r = new Array(n); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1)(2)(3); |
|
} |
|
|
|
r = new Array(n); |
|
start = Date.now(); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1)(2)(3); |
|
} |
|
console.log(Date.now() - start + '\tadd(1)(2)(3)'); |
|
|
|
// Test 4: Partially apply all args except the last, then immediately |
|
// provide the last. Somewhat similar to tests 2 and 3 |
|
r = new Array(n); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1,2)(3); |
|
} |
|
|
|
r = new Array(n); |
|
start = Date.now(); |
|
for(i=0; i<r.length; ++i) { |
|
r[i] = f(1,2)(3); |
|
} |
|
console.log(Date.now() - start + '\tadd(1,2)(3)'); |
|
} |