rubico
Version:
[a]synchronous functional programming
234 lines (229 loc) • 6.78 kB
JavaScript
const isPromise = require('./_internal/isPromise')
const __ = require('./_internal/placeholder')
const curry3 = require('./_internal/curry3')
const genericReduce = require('./_internal/genericReduce')
// _reduce(collection any, reducer function, initial function|any) -> Promise
const _reduce = function (collection, reducer, initial) {
if (typeof initial == 'function') {
const actualInitialValue = initial(collection)
return isPromise(actualInitialValue)
? actualInitialValue.then(curry3(genericReduce, collection, reducer, __))
: genericReduce(collection, reducer, actualInitialValue)
}
return isPromise(initial)
? initial.then(curry3(genericReduce, collection, reducer, __))
: genericReduce(collection, reducer, initial)
}
/**
* @name reduce
*
* @synopsis
* ```coffeescript [specscript]
* type Foldable = Array|Set|Map|Generator|AsyncGenerator|{ reduce: function }|Object
*
* type Reducer = (
* accumulator any,
* item any,
* indexOrKey number|string|any,
* foldable Foldable
* )=>(nextAccumulator Promise|any)
*
* type Resolver = any=>Promise|any
*
* reduce(foldable Promise|Foldable, reducer Reducer) -> accumulator Promise|any
* reduce(foldable Promise|Foldable, reducer Reducer, initialValue Promise|any) -> accumulator Promise|any
* reduce(foldable Promise|Foldable, reducer Reducer, initialResolver Resolver) -> accumulator Promise|any
*
* reduce(reducer Reducer)(foldable Foldable) -> accumulator Promise|any
* reduce(reducer Reducer, initialValue Promise|any)(foldable Foldable) -> accumulator Promise|any
* reduce(reducer Reducer, initialResolver Resolver)(foldable Foldable) -> accumulator Promise|any
* ```
*
* @description
* Reduces a foldable to an accumulated value.
*
* ```javascript [playground]
* const max = (a, b) => a > b ? a : b
*
* const result = reduce([1, 3, 5, 4, 2], max)
*
* console.log(result)
* ```
*
* `reduce` executes a reducer function for each item of a foldable in order. If an initial value is provided, `reduce` starts iterating from the first item of the foldable. If no initial value is provided, `reduce` uses the first item of the foldable as the initial value and starts iterating from the second item of the foldable.
*
* ```javascript [playground]
* const add = (a, b) => a + b
*
* const result = reduce([1, 2, 3, 4, 5], add, 10)
*
* console.log(result)
* ```
*
* The following data types are considered to be foldables:
* * `array`
* * `set`
* * `map`
* * `generator`
* * `async generator`
* * `object with .reduce method`
* * `object`
*
* The reducing operation is expressed by the reducer function and optional initial value, which defines a transformation between an accumulator and a given item of the foldable.
*
* ```javascript
* const reducer = function (accumulator, item) {
* // nextAccumulator is the result of some operation between accumulator and item
* // and becomes the accumulator for the next iteration and invocation of the reducer
* return nextAccumulator
* }
* ```
*
* The reducer function signature changes depending on the provided foldable.
*
* If the foldable is an array:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any,
* index number,
* fold Array
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is a set:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is a map:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any,
* key any,
* fold Map
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is a generator:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is a async generator:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is a plain object:
* ```coffeescript [specscript]
* reducer(
* accumulator any,
* item any,
* key string,
* fold Object
* ) -> nextAccumulator Promise|any
* ```
*
* If the foldable is an object with a `.reduce` method, the reducer function signature is defined externally.
*
* If the reducer is asynchronous, all promises created by the reducer are resolved before continuing with the reducing operation.
*
* ```javascript [playground]
* const asyncAdd = async (a, b) => a + b
*
* const result = await reduce([1, 2, 3, 4, 5], asyncAdd, 0)
*
* console.log(result)
* ```
*
* If the initialization parameter is a function, it is treated as a resolver of the initial value and called with the foldable.
*
* ```javascript [playground]
* const concatSquares = (array, value) => array.concat(value ** 2)
*
* const array = [1, 2, 3, 4, 5]
*
* const result = reduce(array, concatSquares, () => [])
*
* console.log(result)
* ```
*
* `reduce` iterates over just the values of objects and maps.
*
* ```javascript [playground]
* const add = (a, b) => a + b
*
* const object = { a: 1, b: 2, c: 3, d: 4, e: 5 }
* const map = new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]])
*
* const objectResult = reduce(object, add)
* const mapResult = reduce(map, add)
*
* console.log(objectResult)
* console.log(mapResult)
* ```
*
* `reduce` reduces async generators.
*
* ```javascript [playground]
* const add = (a, b) => a + b
*
* const generateAsyncNumbers = async function* () {
* yield 1; yield 2; yield 3; yield 4; yield 5
* }
*
* const result = await reduce(generateAsyncNumbers(), add)
*
* console.log(result)
* ```
*
* If the foldable or initial value is a promise, it is resolved for its value before further execution for the eager interface only.
*
* ```javascript [playground]
* const add = (a, b) => a + b
*
* const resultWithPromiseFoldable = await reduce(Promise.resolve([1, 2, 3, 4, 5]), add, 0)
*
* const resultWithPromiseInitialValue = await reduce([1, 2, 3, 4, 5], add, Promise.resolve(0))
*
* console.log(resultWithPromiseFoldable)
* console.log(resultWithPromiseInitialValue)
* ```
*
* See also:
* * [forEach](/docs/forEach)
* * [map](/docs/map)
* * [filter](/docs/filter)
* * [transform](/docs/transform)
* * [flatMap](/docs/flatMap)
* * [some](/docs/some)
*
* @execution series
*
* @transducing
*
* @TODO readerReduce
*
* @TODO reduce.concurrent
*/
const reduce = function (...args) {
if (typeof args[0] == 'function') {
return curry3(_reduce, __, args[0], args[1])
}
if (isPromise(args[0])) {
return args[0].then(curry3(_reduce, __, args[1], args[2]))
}
return _reduce(args[0], args[1], args[2])
}
module.exports = reduce