Skip to content

Instantly share code, notes, and snippets.

@kputnam
Created July 12, 2012 23:46
Show Gist options
  • Save kputnam/3101848 to your computer and use it in GitHub Desktop.
Save kputnam/3101848 to your computer and use it in GitHub Desktop.
Functional combinators in JavaScript
var u = (function() {
var id = function(x) { return x; }
, single = function(x) { return [x]; }
, constant = function(x) { return function() { return x; }};
var curried = function(f, n, args) {
return (args.length >= n)
? f.apply(null, args)
: function() { return curried(f, n, args.concat(slice(arguments))); }; };
var curry = function(f, n) { return (n || f.length) > 1 ? curried(f, n || f.length, []) : f; }
, slice = function(args, n) { return Array.prototype.slice.call(args, n || 0); }
, apply = function() { return call(arguments); }
, method = function(name) { return ap(bind, attr(name)); }
, invoke = function(name) { return compose(call(slice(arguments, 1)), method(name)); }
, uncurry = function(f) { return function() { return _.reduce(arguments, flip(apply), f); }};
var ap = curry(function(f, g) { return function(x) { return f(x, g(x)); }})
, attr = curry(function(name, object) { return object[name]; })
, flip = curry(function(f, a, b) { return f(b)(a); })
, call = curry(function(args, f) { return f.apply(null, slice(args)); })
, bind = curry(function(ctx, method) { return function() { return method.apply(ctx, slice(arguments)); }})
, compose = curry(function(f, g) { return function(x) { return f(g(x)); }});
return ({id: id, constant: constant, ap: ap, slice: slice, attr: attr,
bind: bind, call: call, flip: flip, apply: apply, compose: compose,
method: method, curry: curry, invoke: invoke, uncurry: uncurry});
})();
var op = ({
'add': u.curry(function(x,y) { return x + y; })
, 'sub': u.curry(function(x,y) { return x - y; })
, 'mul': u.curry(function(x,y) { return x * y; })
, 'div': u.curry(function(x,y) { return x / y; })
, 'mod': u.curry(function(x,y) { return x % y; })
, 'eq': u.curry(function(x,y) { return x === y; })
, 'ne': u.curry(function(x,y) { return x !== y; })
, 'gt': u.curry(function(x,y) { return x > y; })
, 'lt': u.curry(function(x,y) { return x < y; })
, 'lte': u.curry(function(x,y) { return x <= y; })
, 'gte': u.curry(function(x,y) { return x >= y; })
, 'or': u.curry(function(x,y) { return x || y; })
, 'and': u.curry(function(x,y) { return x && y; })
, 'xor': u.curry(function(x,y) { return x !== y; })
, 'neg': function(x) { return -x; }
, 'not': function(x) { return !x; }
, 'typeof': function(x) { return typeof x; }
, 'instanceof': function(x) { return function(y) { return x instanceof y; }}
});
// Where does 'x' occur in each element?
console.log(
_.map(['xbc', 'dxf', 'ghx'], u.invoke('indexOf', 'x')))
// What's the remainder of each element divided by 3?
console.log(
_.map([12345, 23456, 56789], u.flip(op.mod)(3)))
// What's the length of each element?
console.log(
_.map(['abc', 'def', 'ghi'], u.attr('length')))
// Does anyone have an 'o'?
console.log(
_.any(['xbc', 'dxf', 'ghx'], u.compose(op.ne(-1), u.invoke('indexOf', 'o'))))
@kputnam
Copy link
Author

kputnam commented Jul 13, 2012

Point-free approximation of http://jsfiddle.net/ckniffen/d2Xkw/, cc @ckniffen. Note _.pam is handy because, like many Underscore methods, the argument order is reversed from what you'd want to have with partial application.

_.pam = u.flip(_.map);

_.fluck = u.uncurry(u.compose(_.pam, u.invoke))
_.snatch = u.uncurry(u.compose(_.pam, u.attr))

_.fluck('morestuff', [{morestuff: u.constant(10)}, {morestuff: u.constant(20)}])
_.snatch('something', [{something: 'yaya'}, {something: 'okay'}])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment