Skip to content

Instantly share code, notes, and snippets.

@yiransheng
Last active August 29, 2015 14:00
Show Gist options
  • Save yiransheng/11384197 to your computer and use it in GitHub Desktop.
Save yiransheng/11384197 to your computer and use it in GitHub Desktop.
/**
* A dirty hack to achieve true function currying in javascript
**/
var curry = (function() {
/* --->
* wrap everything in a private scope;
*/
/* Generates an array of unique named variable/argument names
* of length k.
*/
var identifiers="abcdefghijklmnopqrstuvwxyz";
function named_args(k) {
var args=[],n = identifiers.length;
while(k--) {
args.push(k.toString(n).split("").map(function(x) {
return identifiers.charAt(parseInt(x, n))
}).join(""))
}
return args
}
/* It takes a function f(x) which returns another function g(y) as a result,
* and yields a new function f′(x,y) which takes a number of additional
* parameters and applies them to the function returned by function f.
* The process can be iterated if necessary.
*/
function uncurry(fn) {
return function() {
var i, f, len = arguments.length;
f = fn;
for(i=0;i<len;i++) {
f = f(arguments[i])
}
return f;
}
}
/* Transforming a function that takes multiple arguments in such a way
* that it becomes a chain of functions, [f(x,y,z) => g(x)(y)(z)]
* each with a single argument, then uncurry the output function,
* so that it can be used with more flexibility, taking any number (less
* than the length of the chain of functions), and return the remainder of
* the chain.
*/
function curry(fn) {
var len = fn.length,
args = named_args(len),
body = 'return fn('+args.join(',')+')',
arg,
f;
// only works on functions with more than one named (of known number) arguments
if (len < 2) return fn;
// has to use eval for function def, as the "Function" constructor
// does not create a nice closure, which we need so that the returned
// function chain has access to the original function
// see: http://www.bennadel.com/blog/1909-javascript-function-constructor-does-not-create-a-closure.htm
eval('f=function ('+args.pop()+'){'+ body + '}');
while(arg=args.pop()) {
eval('f=function ('+arg+'){return uncurry('+f.toString()+ ')}')
}
return uncurry(f)
}
return curry;
//<---
})();
// basic examples
var sum6 = function(x,y,z,u,v,w) {
return x+y+z+u+v+w;
};
var sum6_ = curry(sum6);
sum6_(1)(1)(1)(1)(1)(1) === 6; // true
sum6_(1,1,1,1,1,1) === 6 // true
sum6_(1,1,1)(1,1,1) === 6 //true
var sum5_ = sum6_(2),
sum4_ = sum6_(2,3);
sum4__ = sum6_(2)(3); // they are all functions
sum5_(1,1,1,1,1) === 7; // true
sum4_(1,1,1,1) === 9; // true
sum4__(1)(1)(1,1) === 9; //true
// more "useful" examples
var _reduce = curry(function (fn, memo, arry) {
return arry.reduce(fn, memo)
});
var sum = _reduce(function(s, x){ return s+x }, 0);
var product = _reduce(function(p, x){ return p*x })(1);
sum([1,2,3,4]) === 10 // true
product([1,2,3,4]) === 24 // true
//
var each = curry(function(fn, arry) {
return arry.forEach(fn);
});
var attachElement = curry(function(tagName, parentNode, className, id, html) {
var e = document.createElement(tagName);
e.id = id;
e.className = className;
e.innerHTML = html;
parentNode.appendChild(e);
return e;
});
var createDiv = attachElement('DIV', document.body);
var createColorfulDivs = each(function(d) {
createDiv(d.cls, d.id, d.content);
});
var data=[{ id:1, content: "I am red.", cls: "red"},
{ id:2, content: "I am blue.", cls: "blue"},
{ id:3, content: "I am yellow.", cls: "yellow"},
{ id:4, content: "I am black.", cls: "black"}];
createColorfulDivs(data);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment