Skip to content

Instantly share code, notes, and snippets.

@THEtheChad
Forked from kputnam/u.js
Created July 13, 2012 00:02
Show Gist options
  • Save THEtheChad/3101939 to your computer and use it in GitHub Desktop.
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')))
@THEtheChad
Copy link
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
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
Copy link

kputnam commented Jul 13, 2012

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
Copy link

kputnam commented Jul 13, 2012

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