// Underscore-contrib (underscore.function.combinators.js 0.3.0) // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors // Underscore-contrib may be freely distributed under the MIT license. (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `require` it on the server. if (typeof exports === 'object') { _ = module.exports = require('underscore'); } // Helpers // ------- var existy = function(x) { return x != null; }; var truthy = function(x) { return (x !== false) && existy(x); }; var __reverse = [].reverse; var __slice = [].slice; var __map = [].map; var curry2 = function (fun) { return function curried (first, optionalLast) { if (arguments.length === 1) { return function (last) { return fun(first, last); }; } else return fun(first, optionalLast); }; }; var createPredicateApplicator = function (funcToInvoke /*, preds */) { var preds = _(arguments).tail(); return function (objToCheck) { var array = _(objToCheck).cat(); return _[funcToInvoke](array, function (e) { return _[funcToInvoke](preds, function (p) { return p(e); }); }); }; }; // n.b. depends on underscore.function.arity.js // n.b. depends on underscore.array.builders.js // Takes a target function and a mapping function. Returns a function // that applies the mapper to its arguments before evaluating the body. function baseMapArgs (fun, mapFun) { return _.arity(fun.length, function () { return fun.apply(this, __map.call(arguments, mapFun)); }); } // Mixing in the combinator functions // ---------------------------------- _.mixin({ // Provide "always" alias for backwards compatibility always: _.constant, // Takes some number of functions, either as an array or variadically // and returns a function that takes some value as its first argument // and runs it through a pipeline of the original functions given. pipeline: function(/*, funs */){ var funs = (_.isArray(arguments[0])) ? arguments[0] : arguments; return function(seed) { return _.reduce(funs, function(l,r) { return r(l); }, seed); }; }, // Composes a bunch of predicates into a single predicate that // checks all elements of an array for conformance to all of the // original predicates. conjoin: _.partial(createPredicateApplicator, ('every')), // Composes a bunch of predicates into a single predicate that // checks all elements of an array for conformance to any of the // original predicates. disjoin: _.partial(createPredicateApplicator, 'some'), // Takes a predicate-like and returns a comparator (-1,0,1). comparator: function(fun) { return function(x, y) { if (truthy(fun(x, y))) return -1; else if (truthy(fun(y, x))) return 1; else return 0; }; }, // Returns a function that reverses the sense of a given predicate-like. complement: function(pred) { return function() { return !pred.apply(this, arguments); }; }, // Takes a function expecting varargs and // returns a function that takes an array and // uses its elements as the args to the original // function splat: function(fun) { return function(array) { return fun.apply(this, array); }; }, // Takes a function expecting an array and returns // a function that takes varargs and wraps all // in an array that is passed to the original function. unsplat: function(fun) { var funLength = fun.length; if (funLength < 1) { return fun; } else if (funLength === 1) { return function () { return fun.call(this, __slice.call(arguments, 0)); }; } else { return function () { var numberOfArgs = arguments.length, namedArgs = __slice.call(arguments, 0, funLength - 1), numberOfMissingNamedArgs = Math.max(funLength - numberOfArgs - 1, 0), argPadding = new Array(numberOfMissingNamedArgs), variadicArgs = __slice.call(arguments, fun.length - 1); return fun.apply(this, namedArgs.concat(argPadding).concat([variadicArgs])); }; } }, // Same as unsplat, but the rest of the arguments are collected in the // first parameter, e.g. unsplatl( function (args, callback) { ... ]}) unsplatl: function(fun) { var funLength = fun.length; if (funLength < 1) { return fun; } else if (funLength === 1) { return function () { return fun.call(this, __slice.call(arguments, 0)); }; } else { return function () { var numberOfArgs = arguments.length, namedArgs = __slice.call(arguments, Math.max(numberOfArgs - funLength + 1, 0)), variadicArgs = __slice.call(arguments, 0, Math.max(numberOfArgs - funLength + 1, 0)); return fun.apply(this, [variadicArgs].concat(namedArgs)); }; } }, // map the arguments of a function mapArgs: curry2(baseMapArgs), // Returns a function that returns an array of the calls to each // given function for some arguments. juxt: function(/* funs */) { var funs = arguments; return function(/* args */) { var args = arguments; return _.map(funs, function(f) { return f.apply(this, args); }, this); }; }, // Returns a function that protects a given function from receiving // non-existy values. Each subsequent value provided to `fnull` acts // as the default to the original function should a call receive non-existy // values in the defaulted arg slots. fnull: function(fun /*, defaults */) { var defaults = _.rest(arguments); return function(/*args*/) { var args = _.toArray(arguments); var sz = _.size(defaults); for(var i = 0; i < sz; i++) { if (!existy(args[i])) args[i] = defaults[i]; } return fun.apply(this, args); }; }, // Flips the first two args of a function flip2: function(fun) { return function(/* args */) { var flipped = __slice.call(arguments); flipped[0] = arguments[1]; flipped[1] = arguments[0]; return fun.apply(this, flipped); }; }, // Flips an arbitrary number of args of a function flip: function(fun) { return function(/* args */) { var reversed = __reverse.call(arguments); return fun.apply(this, reversed); }; }, // Takes a method-style function (one which uses `this`) and pushes // `this` into the argument list. The returned function uses its first // argument as the receiver/context of the original function, and the rest // of the arguments are used as the original's entire argument list. functionalize: function(method) { return function(ctx /*, args */) { return method.apply(ctx, _.rest(arguments)); }; }, // Takes a function and pulls the first argument out of the argument // list and into `this` position. The returned function calls the original // with its receiver (`this`) prepending the argument list. The original // is called with a receiver of `null`. methodize: function(func) { return function(/* args */) { return func.apply(null, _.cons(this, arguments)); }; }, k: _.always, t: _.pipeline }); _.unsplatr = _.unsplat; // map the arguments of a function, takes the mapping function // first so it can be used as a combinator _.mapArgsWith = curry2(_.flip(baseMapArgs)); // Returns function property of object by name, bound to object _.bound = function(obj, fname) { var fn = obj[fname]; if (!_.isFunction(fn)) throw new TypeError("Expected property to be a function"); return _.bind(fn, obj); }; }).call(this);