Play with the controls to see how the code works. All code is present in the page, but some scrollback may be necessary to grok everything.
# reducers / transducers / compose
2 min read
Build a map and filter transducer little by little, compose them, and avoid talking about math.
# reduce()
const startWith = []
const oneItem = () => [1]
.reduce(doubleAndAppend, startWith)
const twoItems = () => [1, 2]
.reduce(doubleAndAppend, startWith)
const threeItems = () => [1, 2, 3]
.reduce(doubleAndAppend, startWith)
# doubleAndAppend()
const one = () =>
doubleAndAppend([], 1)
const two = () =>
doubleAndAppend([2], 2)
const three = () =>
doubleAndAppend([2, 4], 3)
function doubleAndAppend(accumulator, x) {
return [...accumulator, x * 2]
}
# double() + append()
const map = () =>
[1, 2, 3].reduce(doubleAndAppend, [])
function doubleAndAppend(accumulator, x) {
return append(accumulator, double(x))
}
function double(x) {
return x * 2
}
function append(accumulator, x) {
return [...accumulator, x]
}
# TransformAndAppend()
const doubleAndAppend = () =>
[1, 2, 3].reduce(
TransformAndAppend(double),
[]
)
function TransformAndAppend(transform) {
return (accumulator, x) =>
append(accumulator, transform(x))
}
# TransformAndBuild() / mapping transducer
const doubleAndAppend = () =>
[1, 2, 3].reduce(
TransformAndBuild(double)(append),
[]
)
function TransformAndBuild(transform) {
return build => {
return (accumulator, x) =>
build(accumulator, transform(x))
}
}
# appendIfDefined()
const excludeUndefined = () =>
[1, 2, undefined, 3].reduce(
TransformAndBuild(x => x)(
appendIfDefined
),
[]
)
function appendIfDefined(accumulator, x) {
return defined(x)
? [...accumulator, x]
: accumulator
}
function defined(x) {
return x !== undefined
}
# map() + filter() transducers
const doubleAndAppend =
map(double)(append)
const appendIfDefined =
filter(defined)(append)
const runMap = () =>
[1, 2, 3]
.reduce(doubleAndAppend, [])
const runFilter = () =>
[1, 2, undefined, 3]
.reduce(appendIfDefined, [])
function map(transform) {
return build => (acc, x) =>
build(acc, transform(x))
}
function filter(predicate) {
return build => (acc, x) =>
predicate(x) ? build(acc, x) : acc
}
# compose()
const doubleNumbers = map(double)
const onlyDefined = filter(defined)
const manually = () =>
[1, 2, undefined, 3].reduce(
onlyDefined(doubleNumbers(append)),
[]
)
const functionally = () =>
[1, 2, undefined, 3].reduce(
compose(
onlyDefined,
doubleNumbers
)(append),
[]
)
function compose(...fns) {
return x =>
fns.reduceRight((y, f) => {
const acc = f(y)
return acc
}, x)
}