Skip to content

Instantly share code, notes, and snippets.

@THEtheChad
Forked from kputnam/u.js
Created July 13, 2012 00:02
Show Gist options
  • Select an option

  • Save THEtheChad/3101939 to your computer and use it in GitHub Desktop.

Select an option

Save THEtheChad/3101939 to your computer and use it in GitHub Desktop.
Functional combinators in JavaScript
var u = (function() {
var id = function(x) { return x; }
var constant = function(x) { return function() { return x; }}
var ap = function(f,g) { return function(x) { return f(x)(g(x)); }}
var slice = function(args, n) { return Array.prototype.slice.call(args, n || 0); }
var attr = function(name) { return function(object) { return object[name]; }}
var bind = function(ctx) { return function(method) { return function() { return method.apply(ctx, slice(arguments)); }}}
var call = function(args) { return function(func) { return func.apply(null, slice(args)); }}
var flip = function(func) { return function(a) { return function(b) { return func(b)(a); }}}
var apply = function() { return call(slice(arguments)); }
var compose = function(f,g) { return function(x) { return f(g(x)); }}
var method = function(name) { return ap(bind, attr(name)); }
var invoke = function(name) { return compose(call(slice(arguments, 1)), method(name)); }
var curry = function(f,n) {
if (n === 0) return f;
var args = []
, last = function(x) { args.push(x); return f.apply(null, args); }
, fold = function(g) { return function(x) { args.push(x); return g; }};
return _.reduce(_.range(n-1), fold, last); }
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});
})();
var op = ({
'add': u.curry(function(x,y) { return x + y; }, 2)
, 'sub': u.curry(function(x,y) { return x - y; }, 2)
, 'mul': u.curry(function(x,y) { return x * y; }, 2)
, 'div': u.curry(function(x,y) { return x / y; }, 2)
, 'mod': u.curry(function(x,y) { return x % y; }, 2)
, 'eq': u.curry(function(x,y) { return x === y; }, 2)
, 'ne': u.curry(function(x,y) { return x !== y; }, 2)
, 'gt': u.curry(function(x,y) { return x > y; }, 2)
, 'lt': u.curry(function(x,y) { return x < y; }, 2)
, 'lte': u.curry(function(x,y) { return x <= y; }, 2)
, 'gte': u.curry(function(x,y) { return x >= y; }, 2)
, 'or': u.curry(function(x,y) { return x || y; }, 2)
, 'and': u.curry(function(x,y) { return x && y; }, 2)
, 'xor': u.curry(function(x,y) { return x !== y; }, 2)
, '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?
_.map(['xbc', 'dxf', 'ghx'], u.invoke('indexOf', 'x'))
// What's the remainder of each element divided by 3?
_.map([12345, 23456, 56789], u.flip(op.mod)(3))
// What's the length of each element?
_.map(['abc', 'def', 'ghi'], u.attr('length'))
// Does anyone have an 'o'?
_.any(['xbc', 'dxf', 'ghx'], u.compose(op.ne(-1), u.invoke('indexOf', 'o')))
@kputnam

kputnam commented Jul 13, 2012

Copy link
Copy Markdown

Look out, there's a nasty bug in curry. I've fixed it in a later revision, but see if you can find it :-)

@THEtheChad

THEtheChad commented Jul 13, 2012 via email

Copy link
Copy Markdown
Author

@kputnam

kputnam commented Jul 13, 2012

Copy link
Copy Markdown
var curry = function(f,n) {
    if (n === 0) return f;
    var args = []
      , last = function(x) { args.push(x); return f.apply(null, args); }
      , fold = function(g) { return function(x) { args.push(x); return g; }};
    return _.reduce(_.range(n-1), fold, last); }

var xyz = curry(function(x,y,z) { return x + y + z; });
xyz(1)(2)(3) //=> 6
xyz(1)(2)(0) //=> 6 ... whoops!

@THEtheChad

THEtheChad commented Jul 13, 2012 via email

Copy link
Copy Markdown
Author

@THEtheChad

Copy link
Copy Markdown
Author
var curry = function(f,n) {
  if (n === 0) return f;
  var args = []
    , last = function(x) { args.push(x); var c = _.clone(args); args.length = 0; return f.apply(null, c); }
    , fold = function(g) { return function(x) { args.push(x); return g; }};
  return _.reduce(_.range(n-1), fold, last); }

@THEtheChad

Copy link
Copy Markdown
Author

Rewrote the function to clone the arguments list and reset the size to 0. You get better performance by altering the length instead of creating a new object because the browser doesn't have to perform garbage collection on the old one.

@kputnam

kputnam commented Jul 13, 2012

Copy link
Copy Markdown

Yep, that seems to fix the bug.

  var curried = function(f, n, args) {
    return (args.length >= n)
      ? f.apply(null, args)
      : function() { return curried(f, n, args.concat(slice(arguments))); }; };

  var slice  = function(args, n) { return Array.prototype.slice.call(args, n || 0); }
  var curry = function(f, n)     { return (n <= 0) ? f : curried(f, n, []); }

Though this alternate definition has a nice property: no burdensome syntax penalty when you don't need partial application.

var xyz = curry(function(x,y,z) { return x + y + z; }, 3);
xyz(1)(2)(3) == xyz(1,2,3);

@kputnam

kputnam commented Jul 13, 2012

Copy link
Copy Markdown

Oh, I noticed your fix is still incomplete. This is a classic case where mutable state can really be a pain in the ass.

var xyz = curry(function(x,y,z) { return x + y + z; }, 3);
var z = xyz(1)(2);
z(3) //=> 3
z(3) //=> NaN

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