Add _.consume and _.consumeRight and make _.compose an alias of _.consumeRight. [closes #667]

This commit is contained in:
John-David Dalton
2014-08-24 12:17:50 -07:00
parent c17e1f9e75
commit c5112cb24e
2 changed files with 111 additions and 59 deletions

106
lodash.js
View File

@@ -5916,62 +5916,98 @@
} }
/** /**
* Creates a function that is the composition of the provided functions, * Creates a function that invokes the provided functions with the `this`
* where each function consumes the return value of the function that follows. * binding of the create function, where each successive invocation consumes
* For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * the return value of the previous.
* Each function is executed with the `this` binding of the composed function.
* *
* @static * @static
* @memberOf _ * @memberOf _
* @category Function * @category Function
* @param {...Function} [funcs] Functions to compose. * @param {...Function} [funcs] Functions to invoke.
* @returns {Function} Returns the new composed function. * @returns {Function} Returns the new function.
* @example * @example
* *
* var realNameMap = { * function add(x, y) {
* 'pebbles': 'penelope' * return x + y;
* }; * }
* *
* var format = function(name) { * function square(n) {
* name = realNameMap[name.toLowerCase()] || name; * return n * n;
* return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); * }
* };
* *
* var greet = function(formatted) { * var addSquare = _.consume(add, square);
* return 'Hiya ' + formatted + '!'; * addSquare(1, 2);
* }; * // => 9
*
* var welcome = _.compose(greet, format);
* welcome('pebbles');
* // => 'Hiya Penelope!'
*/ */
function compose() { function consume() {
var funcs = arguments, var funcs = arguments,
length = funcs.length, length = funcs.length;
index = length - 1;
if (!length) { if (!length) {
return function() {}; return function() {};
} }
while (length--) { if (!arrayEvery(funcs, isFunction)) {
if (!isFunction(funcs[length])) { throw new TypeError(FUNC_ERROR_TEXT);
throw new TypeError(FUNC_ERROR_TEXT);
}
} }
return function() { return function() {
length = index; var index = 0,
var result = funcs[length].apply(this, arguments); result = funcs[index].apply(this, arguments);
while (length--) { while (++index < length) {
result = funcs[length].call(this, result); result = funcs[index].call(this, result);
} }
return result; return result;
}; };
} }
/** /**
* Creates a function which accepts one or more arguments of `func` that when * This method is like `_.consume` except that it creates a function that
* invoked either executes `func` returning its result, if all `func` arguments * invokes the provided functions from right to left.
*
* @static
* @memberOf _
* @alias compose
* @category Function
* @param {...Function} [funcs] Functions to invoke.
* @returns {Function} Returns the new function.
* @example
*
* function add(x, y) {
* return x + y;
* }
*
* function square(n) {
* return n * n;
* }
*
* var addSquare = _.consumeRight(square, add);
* addSquare(1, 2);
* // => 9
*/
function consumeRight() {
var funcs = arguments,
fromIndex = funcs.length - 1;
if (fromIndex < 0) {
return function() {};
}
if (!arrayEvery(funcs, isFunction)) {
throw new TypeError(FUNC_ERROR_TEXT);
}
return function() {
var index = fromIndex,
result = funcs[index].apply(this, arguments);
while (index--) {
result = funcs[index].call(this, result);
}
return result;
};
}
/**
* Creates a function that accepts one or more arguments of `func` that when
* called either invokes `func` returning its result if all `func` arguments
* have been provided, or returns a function that accepts one or more of the * have been provided, or returns a function that accepts one or more of the
* remaining `func` arguments, and so on. The arity of `func` can be specified * remaining `func` arguments, and so on. The arity of `func` can be specified
* if `func.length` is not sufficient. * if `func.length` is not sufficient.
@@ -9303,7 +9339,8 @@
lodash.chain = chain; lodash.chain = chain;
lodash.chunk = chunk; lodash.chunk = chunk;
lodash.compact = compact; lodash.compact = compact;
lodash.compose = compose; lodash.consume = consume;
lodash.consumeRight = consumeRight;
lodash.constant = constant; lodash.constant = constant;
lodash.countBy = countBy; lodash.countBy = countBy;
lodash.create = create; lodash.create = create;
@@ -9384,6 +9421,7 @@
// add aliases // add aliases
lodash.collect = map; lodash.collect = map;
lodash.compose = consumeRight;
lodash.each = forEach; lodash.each = forEach;
lodash.eachRight = forEachRight; lodash.eachRight = forEachRight;
lodash.extend = assign; lodash.extend = assign;

View File

@@ -1722,52 +1722,64 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.compose'); QUnit.module('lodash.consumeRight');
(function() { (function() {
test('should create a function that is the composition of the provided functions', 1, function() { test('should be aliased', 1, function() {
var realNameMap = { strictEqual(_.compose, _.consumeRight);
'pebbles': 'penelope' });
}; }());
var format = function(name) { /*--------------------------------------------------------------------------*/
name = realNameMap[name.toLowerCase()] || name;
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
};
var greet = function(formatted) { QUnit.module('consume methods');
return 'Hiya ' + formatted + '!';
};
var welcome = _.compose(greet, format); _.each(['consume', 'consumeRight'], function(methodName) {
strictEqual(welcome('pebbles'), 'Hiya Penelope!'); var func = _[methodName],
isConsume = methodName == 'consume';
test('`_.' + methodName + '` should create a function that consumes the output of the provided functions', 1, function() {
function add(x, y) {
return x + y;
}
function square(n) {
return n * n;
}
function fixed(n) {
return n.toFixed(1);
}
var consumer = isConsume ? func(add, square, fixed) : func(fixed, square, add);
strictEqual(consumer(1, 2), '9.0');
}); });
test('should return a new function', 1, function() { test('`_.' + methodName + '` should return a new function', 1, function() {
notStrictEqual(_.compose(_.noop), _.noop); notStrictEqual(func(_.noop), _.noop);
}); });
test('should return a noop function when no arguments are provided', 2, function() { test('`_.' + methodName + '` should return a noop function when no arguments are provided', 2, function() {
var composed = _.compose(); var consumer = func();
try { try {
strictEqual(composed(), undefined); strictEqual(consumer(), undefined);
} catch(e) { } catch(e) {
ok(false); ok(false);
} }
notStrictEqual(composed, _.noop); notStrictEqual(consumer, _.noop);
}); });
test('should return a wrapped value when chaining', 1, function() { test('`_.' + methodName + '` should return a wrapped value when chaining', 1, function() {
if (!isNpm) { if (!isNpm) {
var actual = _(_.noop).compose(); var actual = _(_.noop)[methodName]();
ok(actual instanceof _); ok(actual instanceof _);
} }
else { else {
skipTest(); skipTest();
} }
}); });
}()); });
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -12060,6 +12072,8 @@
'before', 'before',
'bind', 'bind',
'compose', 'compose',
'consume',
'consumeRight',
'curry', 'curry',
'curryRight', 'curryRight',
'debounce', 'debounce',
@@ -12143,13 +12157,13 @@
}); });
}); });
test('should throw a TypeError for falsey arguments', 17, function() { test('should throw a TypeError for falsey arguments', 19, function() {
_.each(rejectFalsey, function(methodName) { _.each(rejectFalsey, function(methodName) {
var expected = _.map(falsey, _.constant(true)), var expected = _.map(falsey, _.constant(true)),
func = _[methodName]; func = _[methodName];
var actual = _.map(falsey, function(value, index) { var actual = _.map(falsey, function(value, index) {
var pass = !index && methodName == 'compose'; var pass = !index && /^(?:compose|consume(Right)?)$/.test(methodName);
try { try {
index ? func(value) : func(); index ? func(value) : func();
} catch(e) { } catch(e) {