Last active
March 16, 2018 00:43
-
-
Save quidmonkey/1e37716db7d4e1a6af5181d8fb0f4a6c to your computer and use it in GitHub Desktop.
Various JavaScript Implementations of Reduce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// utils | |
// unit test - assert 2 values are equal | |
const assert = (assertion, actual, expected) => { | |
console.log('~~~', assertion, ':', actual === expected); | |
}; | |
// clone a value | |
// objects are only shallow cloned | |
const clone = (val) => { | |
if (Array.isArray(val)) { | |
return [...val]; | |
} else if (typeof val === 'object' && val != null) { | |
return {...val}; | |
} | |
return val; | |
}; | |
// functional compose | |
const compose = (...fs) => fs.reduce((f, g) => (...args) => f(g(...args))); | |
// generator runner | |
const spawn = (gen) => { | |
return (...args) => { | |
const it = gen.apply(this, args); | |
return Promise.resolve() | |
.then(function step(val) { | |
const res = it.next(val); | |
if (res.done) { | |
return res.value; | |
} | |
return Promise.resolve(res.value) | |
.then(step) | |
.catch(it.throw.bind(it)); | |
}); | |
}; | |
}; | |
// reducers | |
// simple synchronous reducer | |
const reduce = (reducer, initialValue, list) => { | |
let accumulator = initialValue; | |
for (let i = 0; i < list.length; i++) { | |
accumulator = reducer( | |
accumulator, | |
clone(list[i]), | |
i, | |
clone(list) | |
); | |
} | |
return accumulator; | |
}; | |
// synchronous reducer using composition | |
const reduceFunctional = (reducer, initialValue, list) => { | |
const reducers = Array(list.length).fill( | |
([accumulator, currentIndex]) => { | |
const reducedValue = reducer( | |
accumulator, | |
clone(list[currentIndex]), | |
currentIndex, | |
clone(list) | |
); | |
return [reducedValue, currentIndex + 1]; | |
} | |
); | |
return compose(([accumulator]) => accumulator, ...reducers)([initialValue, 0]); | |
}; | |
// async/await reducer | |
const reduceAsync = async (reducer, initialValue, list) => { | |
let accumulator = initialValue; | |
let currentIndex = -1; | |
for (const currentValue of list) { | |
accumulator = await reducer( | |
accumulator, | |
clone(currentValue), | |
currentIndex++, | |
clone(list) | |
); | |
} | |
return accumulator; | |
}; | |
// generator reducer | |
const reduceGenerator = spawn(function* (reducer, initialValue, list) { | |
let accumulator = initialValue; | |
let currentIndex = -1; | |
for (const currentValue of list) { | |
accumulator = yield reducer( | |
accumulator, | |
clone(currentValue), | |
currentIndex++, | |
clone(list) | |
); | |
} | |
return accumulator; | |
}); | |
// promise reducer | |
// thanks to https://github.com/tomdottom for this implementation | |
const reducePromise = (reducer, initialValue, list) => { | |
let promise = Promise.resolve(initialValue); | |
for (const currentIndex in list) { | |
promise = promise.then((accumulator) => { | |
return reducer( | |
accumulator, | |
clone(list[currentIndex]), | |
currentIndex, | |
clone(list) | |
); | |
}); | |
} | |
return promise; | |
}; | |
// promise reducer using sync reduce as a proxy | |
const reducePromiseProxy = (reducer, initialValue, list) => { | |
return reduce( | |
(...args) => { | |
return Promise.resolve(args[0]) | |
.then((accumulator) => { | |
return reducer(accumulator, ...args.slice(1)); | |
}); | |
}, | |
initialValue, | |
list | |
); | |
}; | |
// promise reducer using recursion | |
const reducePromiseRecurse = (reducer, initialValue, list) => { | |
const iterate = (accumulator, currentIndex) => { | |
return Promise.resolve() | |
.then(() => { | |
if (currentIndex < list.length) { | |
return reducer( | |
accumulator, | |
clone(list[currentIndex]), | |
currentIndex, | |
clone(list) | |
) | |
.then((newAccumulator) => { | |
return iterate(newAccumulator, currentIndex + 1); | |
}); | |
} | |
return accumulator; | |
}); | |
}; | |
return iterate(initialValue, 0); | |
}; | |
// unit tests | |
const syncTest = (assertion, unit) => { | |
const numbers = [...Array(5).keys()]; | |
const reducer = (accumulator, currentValue) => { | |
return accumulator + currentValue; | |
}; | |
const expected = 10; | |
const actual = unit(reducer, 0, numbers); | |
assert(assertion, actual, expected); | |
}; | |
// uses async/await & promises to simplify the test | |
const asyncTest = async (assertion, unit) => { | |
const numbers = [...Array(5).keys()]; | |
const reducer = async (accumulator, currentValue) => { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve(accumulator + currentValue); | |
}, 200); | |
}); | |
}; | |
const expected = 10; | |
const actual = await unit(reducer, 0, numbers); | |
assert(assertion, actual, expected); | |
}; | |
// magic! | |
const main = async () => { | |
syncTest('reduce: should sum a list of numbers synchonously', reduce); | |
syncTest('reduceFunctional: should sum a list of numbers synchronously using composition', reduceFunctional); | |
await asyncTest('reduceAsync: should sum a list of numbers using async/await', reduceAsync); | |
await asyncTest('reduceGenerator: should sum a list of numbers using generators', reduceGenerator); | |
await asyncTest('reducePromise: should sum a list of numbers using promises', reducePromise); | |
await asyncTest('reducePromiseProxy: should sum a list of numbers using promises proxied to the reduce sync', reducePromiseProxy); | |
await asyncTest('reducePromiseRecurse: should sum a list of numbers using promises', reducePromiseRecurse); | |
}; | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment