Skip to content

Commit

Permalink
feat: add reduceRightP
Browse files Browse the repository at this point in the history
Closes #114
  • Loading branch information
char0n committed Aug 13, 2017
1 parent fbc373f commit 81d3d09
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 1 deletion.
26 changes: 25 additions & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,10 @@ declare namespace RamdaAdjunct {
curryRight(fn: Function): Function

/**
*
* Given an `Iterable`(arrays are `Iterable`), or a promise of an `Iterable`,
* which produces promises (or a mix of promises and values),
* iterate over all the values in the `Iterable` into an array and
* reduce the array to a value using the given iterator function.
*/
reduceP<T, TResult, R extends T[]>(fn: (acc: TResult, elem: T) => TResult, acc: TResult, list: R): TResult;
reduceP<T, TResult, R extends T[]>(fn: (acc: TResult, elem: T) => TResult, acc: TResult): {
Expand All @@ -370,6 +373,27 @@ declare namespace RamdaAdjunct {
}
}

/**
* Given an `Iterable`(arrays are `Iterable`), or a promise of an `Iterable`,
* which produces promises (or a mix of promises and values),
* iterate over all the values in the `Iterable` into an array and
* reduce the array to a value using the given iterator function.
*
* Similar to {@link RA.reduceP|reduceP} except moves through the input list from the right to the left.
* The iterator function receives two values: (value, acc),
* while the arguments' order of reduceP's iterator function is (acc, value).
*/
reduceRightP<T, TResult, R extends T[]>(fn: (elem: T, acc: TResult) => TResult, acc: TResult, list: R): TResult;
reduceRightP<T, TResult, R extends T[]>(fn: (elem: T, acc: TResult) => TResult, acc: TResult): {
(list: R): TResult
};
reduceRightP<T, TResult, R extends T[]>(fn: (elem: T, acc: TResult) => TResult): {
(acc: TResult, list: R): TResult;
(acc: TResult): {
(list: R): TResult
}
}

/**
* Identity type.
*/
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import pickIndexes from './pickIndexes';
import list from './list';
import concatRight from './concatRight';
import reduceP from './reduceP';
import reduceRightP from './reduceRightP';
// Object
import paths from './paths';
import renameKeys from './renameKeys';
Expand Down Expand Up @@ -125,6 +126,7 @@ export { default as pickIndexes } from './pickIndexes';
export { default as list } from './list';
export { default as concatRight } from './concatRight';
export { default as reduceP } from './reduceP';
export { default as reduceRightP } from './reduceRightP';
// Object
export { default as paths } from './paths';
export { default as renameKeys } from './renameKeys';
Expand Down Expand Up @@ -201,6 +203,7 @@ const RA = {
list,
concatRight,
reduceP,
reduceRightP,
// Object
defaults: mergeRight,
resetToDefault: mergeRight,
Expand Down
92 changes: 92 additions & 0 deletions src/reduceRightP.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { curryN, reduceRight, length } from 'ramda';

import isUndefined from './isUndefined';

/* eslint-disable max-len */
/**
* Given an `Iterable`(arrays are `Iterable`), or a promise of an `Iterable`,
* which produces promises (or a mix of promises and values),
* iterate over all the values in the `Iterable` into an array and
* reduce the array to a value using the given iterator function.
*
* Similar to {@link RA.reduceP|reduceP} except moves through the input list from the right to the left.
* The iterator function receives two values: (value, acc),
* while the arguments' order of reduceP's iterator function is (acc, value).
*
* @func reduceRightP
* @memberOf RA
* @since {@link https://char0n.github.io/ramda-adjunct/1.13.0|v1.13.0}
* @category List
* @sig
*
* ((MaybePromise b, Promise a) -> Promise a) -> MaybePromise a -> MaybePromise [MaybePromise b] -> Promise a
* MaybePromise = Promise.<*> | *
*
* @param {Function} fn The iterator function. Receives two values, the current element from the list and the accumulator
* @param {*|Promise.<*>} acc The accumulator value
* @param {Array.<*>|Promise.<Array<*|Promise.<*>>>} list The list to iterate over
* @return {Promise} The final, accumulated value
* @see {@link RA.reduceP|reduceP}, {@link http://bluebirdjs.com/docs/api/promise.reduce.html|bluebird.reduce}
* @example
*
* RA.reduceRightP(
* (fileName, total) => fs
* .readFileAsync(fileName, 'utf8')
* .then(contents => total + parseInt(contents, 10)),
* 0,
* ['file1.txt', 'file2.txt', 'file3.txt']
* ); // => Promise(10)
*
* RA.reduceRightP(
* (fileName, total) => fs
* .readFileAsync(fileName, 'utf8')
* .then(contents => total + parseInt(contents, 10)),
* Promise.resolve(0),
* ['file1.txt', 'file2.txt', 'file3.txt']
* ); // => Promise(10)
*
* RA.reduceRightP(
* (fileName, total) => fs
* .readFileAsync(fileName, 'utf8')
* .then(contents => total + parseInt(contents, 10)),
* 0,
* [Promise.resolve('file1.txt'), 'file2.txt', 'file3.txt']
* ); // => Promise(10)
*
* RA.reduceRightP(
* (fileName, total) => fs
* .readFileAsync(fileName, 'utf8')
* .then(contents => total + parseInt(contents, 10)),
* 0,
* Promise.resolve([Promise.resolve('file1.txt'), 'file2.txt', 'file3.txt'])
* ); // => Promise(10)
*
*/
/* esline-enable max-len */
const reduceRightP = curryN(3, (fn, acc, list) => {
const originalAccP = Promise.resolve(acc);
const listLength = length(list);

if (listLength === 0) {
return originalAccP;
}

return Promise.resolve(list).then((iterable) => {
const reducer = reduceRight((currentValueP, accP) =>
accP
.then(previousValue => Promise.all([previousValue, currentValueP]))
.then(([previousValue, currentValue]) => {
if (isUndefined(previousValue) && listLength === 1) {
return currentValue;
}

return fn(currentValue, previousValue);
})
);

return reducer(originalAccP, iterable);
});
});


export default reduceRightP;
122 changes: 122 additions & 0 deletions test/reduceRightP.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import R from 'ramda';
import sinon from 'sinon';

import RA from '../src/index';
import eq from './shared/eq';


describe.only('reduceRightP', function() {
it('folds simple functions over arrays with the supplied accumulator', function() {
const testAdd = RA.reduceRightP(R.add, 0, [1, 2, 3, 4]).then(actual => eq(actual, 10));
const testMultiply = RA.reduceRightP(R.multiply, 1, [1, 2, 3, 4])
.then(actual => eq(actual, 24));

return Promise.all([testAdd, testMultiply]);
});

it('should not dispatch to objects that implement `reduce`', function() {
const obj = { x: [1, 2, 3], reduce() { return 'override' } };
const test1 = RA.reduceRightP(R.add, 0, obj).then(actual => eq(actual, 0));
const test2 = RA.reduceRightP(R.add, 10, obj).then(actual => eq(actual, 10));

return Promise.all([test1, test2]);
});

it('returns the accumulator for an empty array', function() {
const testAdd = RA.reduceRightP(R.add, 0, []).then(actual => eq(actual, 0));
const testMultiply = RA.reduceRightP(R.multiply, 1, []).then(actual => eq(actual, 1));
const testConcat = RA.reduceRightP(R.concat, [], []).then(actual => eq(actual, []));

return Promise.all([testAdd, testMultiply, testConcat]);
});

it('is curried', function() {
const sum = RA.reduceRightP(R.add)(0);
const cat = RA.reduceRightP(R.concat)('');

const testSum = sum([1, 2, 3, 4]).then(actual => eq(actual, 10));
const testConcat = cat(['1', '2', '3', '4']).then(actual => eq(actual, '1234'));

return Promise.all([testSum, testConcat]);
});

it('correctly reports the arity of curried versions', function() {
const sum = RA.reduceRightP(R.add, 0);
eq(sum.length, 1);
});

it('tests initial value for promise', function() {
const testAdd = RA.reduceRightP(R.add, Promise.resolve(0), [1, 2, 3, 4])
.then(actual => eq(actual, 10));
const testMultiply = RA.reduceRightP(R.multiply, Promise.resolve(1), [1, 2, 3, 4])
.then(actual => eq(actual, 24));

return Promise.all([testAdd, testMultiply]);
});

it('tests returning initial value when iterable is empty', function() {
const add = sinon.spy();

return RA.reduceRightP(add, 0, [])
.then(actual => eq(actual, 0))
.then(() => eq(add.called, false));
});

it('tests returning initial value when iterable is empty (promise version)', function() {
const add = sinon.spy();

return RA.reduceRightP(add, Promise.resolve(0), [])
.then(actual => eq(actual, 0))
.then(() => eq(add.called, false));
});

it('tests if initial value is undefined', function() {
const add = sinon.spy();

return RA.reduceRightP(add, undefined, [1])
.then(actual => eq(actual, 1))
.then(() => eq(add.called, false));
});

it('tests if initial value is undefined (promise version)', function() {
const add = sinon.spy();

return RA.reduceRightP(add, Promise.resolve(), [1])
.then(actual => eq(actual, 1))
.then(() => eq(add.called, false));
});

it('tests iterator wrapped in the promise', function() {
return RA.reduceRightP(R.add, 0, Promise.resolve([1, 2, 3])).then(actual => eq(actual, 6));
});

it('tests iterator containing values and promises', function() {
return RA.reduceRightP(R.add, 0, [1, Promise.resolve(2), 3]).then(actual => eq(actual, 6));
});

it('tests iterator wrapped in promise containing values and promises', function() {
return RA.reduceRightP(R.add, 0, Promise.resolve([1, Promise.resolve(2), 3]))
.then(actual => eq(actual, 6));
});

it('tests iterator function returning promises', function() {
return RA.reduceRightP(
R.pipe(R.add, Promise.resolve.bind(Promise)),
0,
Promise.resolve([1, Promise.resolve(2), 3])
)
.then(actual => eq(actual, 6));
});

it('tests difference between reduceRightP reduceP', function() {
const cat = RA.reduceP(R.concat)('');
const catRight = RA.reduceRightP(R.concat)('');
const catRightFlipped = RA.reduceRightP(R.flip(R.concat))('');

const testCat = cat(['1', '2', '3', '4']).then(actual => eq(actual, '1234'));
const testCatRight = catRight(['1', '2', '3', '4']).then(actual => eq(actual, '1234'));
const testCatRightFlipped = catRightFlipped(['1', '2', '3', '4']).then(actual => eq(actual, '4321'));

return Promise.all([testCat, testCatRight, testCatRightFlipped]);
});
});

0 comments on commit 81d3d09

Please sign in to comment.