const identity = (context) => context
const map = (mappingFn) => (context = []) =>
context.map(mappingFn)
const alwaysTrue = (...contexts) => true
const alwaysFalse = (...contexts) => false
The counter-intuative patterns that will simplify your code, help you ship fast, be more stable, and grow a thicker, fuller, healthier hair.
an act or mechanism to combine simple functions to build more complicated ones
https://en.wikipedia.org/wiki/Function_composition_(computer_science)
const doesStuff = (context) =>
{ ... }
const doesOtherStuff = (context) =>
{ ... }
const whyNotBoth = (context) =>
doesStuff(doesOtherStuff(context))
// We use function composition
// in React a lot
connect(...)(withRouter(Component))
the process of breaking a complex problem or system into parts that are easier to conceive, understand, program, and maintain
https://en.wikipedia.org/wiki/Decomposition_(computer_science)
const doManyThings = (context) => {
const clone = { ...context }
clone.foo = 'bar'
console.log(clone)
if (!clone.isValid) {
throw new Error('Not Valid')
}
return clone
}
const doManyThings =
assert(
({ isValid }) => isValid,
throw new Error('Not Valid')
)(
log(
assign({ foo: 'bar' })(
clone(
context
)
)
)
)
Rather than spelling out every step of how to do something, how about we just declare what, and not worry about how each what gets accomplished, at least for now?
const doManyThings = (context) =>
context ->
clone ->
assign({ foo: 'bar' }) ->
log ->
assert(
({ isValid }) => isValid,
throw new Error('Not Valid')
)
const doManyThings = (context) =>
context |
clone |
assign({ foo: 'bar' }) |
log |
assert(
({ isValid }) => isValid,
throw new Error('Not Valid')
)
I spent much of my 7th grade building rube-goldberg-esque pipe layouts on windows 3.1 😱
Â
Now I spend much of my professional time building data pipelines.
Â
Some kids never grow up.
the innermost value is passed to the innermost function. It's return valued is passed as the argument to the next innermost function, etc.
const doManyThings =
assert(
({ isValid }) => isValid,
throw new Error('Not Valid')
)(
log(
assign({ foo: 'bar' })(
clone(
context
)
)
)
)
const doManyThings = (context) =>
assert({ isValid }) => isValid,
throw new Error('Not Valid')
) <-
log <-
assign({ foo: 'bar' }) <-
clone <-
context
const doManyThings = (x) =>
f(g(h(i(x))))
const doManyThings = (x) =>
f <- g <- h <- i <- x
It's not a pipe (see what I did there?)
many libraries include a compose function including redux, lodash, underscore, ramda, recompose, ...
Â
It helps to see an implementation to understand it.
const compose = (...fns) => (context) =>
fns.reduceRight((prevVal, nextFn =>
nextFn(prevVal),
context
)
const pipe = (...fns) => (context) =>
fns.reduce((prevVal, nextFn =>
nextFn(prevVal),
context
)
Function composition is cool and all, but we haven't really answered why someone would feel the need to rewrite a built in function like map or filter when they're already on the array prototype.
const identity = (context) => context
const map = (mappingFn) => (context) =>
context.map(mappingFn)
const filter = (filterFn) => (context) =>
context.filter(filterFn)
Function composition's strengths lies in its constraints.
Â
The context is the subject of the function. The thing upon which the function is operating. The data.
array.map((item) => { ... })
array.filter((item) => { ... })
The context is what is passed through the pipeline. Therefore, it needs to be an argument.
(array) => array.map((item) => { ... })
(array) => array.filter((item) => { ... })
What if you need more than just the context -- if you need to configure your function?
const map = (array, mappingFn) =>
array.map(mappingFn)
const map = (mappingFn, array) =>
array.map(mappingFn)
const map = (mappingFn) => (array) =>
array.map(mappingFn)
To make the function composable, have the function return after accepting everything except the context. Have it return a function that then only accepts the context.
You still have access to those config arguments inside the closure
Make the context the last argument accepted
(array) => array.map((item) => { ... })
const identity = (context) => context
const map = (mappingFn) => (context = []) =>
context.map(mappingFn)
const alwaysTrue = (...contexts) => true
const alwaysFalse = (...contexts) => false
const identity = (context) => context
const map = (mappingFn) => (context = []) =>
context.map(mappingFn)
const alwaysTrue = (...contexts) => true
const alwaysFalse = (...contexts) => false
<ThemeProvider theme={theme}>
<BrowserRouter>
<Main>
<Nav>
<NavItem>
<a to='/home'>
<span>Home</span>
</a>
</NavItem>
</Nav>
</Main>
</BrowserRouter>
</ThemeProvider>
React.createElement(
ThemeProvider, { theme: theme }, React.createElement(
BrowserRouter, null, React.createElement(
Main, null, React.createElement(
Nav, null, React.createElement(
NavItem, null, React.createElement(
'a', { to: '/home' }, React.createElement(
'span', null, 'Home'
)
)
)
)
)
)
)
pipe(
React.createElement('a', { to: '/home' }),
React.createElement('span', null),
React.createElement(NavItem, null),
React.createElement(Nav, null),
React.createElement(Main, null),
React.createElement(BrowserRouter, null),
React.createElement(ThemeProvider, { theme: theme })
)('Home')
compose(
React.createElement(ThemeProvider, { theme: theme })
React.createElement(BrowserRouter, null),
React.createElement(Main, null),
React.createElement(Nav, null),
React.createElement(NavItem, null),
React.createElement('span', null),
React.createElement('a', { to: '/home' }),
)('Home')
app.use((req, res, next) => {
...
next()
})
app.use(someSpecializedThing)
app.use(otherSpecializedThing)
app.use(nextSpecializedThing)
app.use(sendSomethingBack)
someSpecializedThing ->
otherSpecializedThing ->
nextSpecializedThing ->
sendSomethingBack
pipe(
someSpecializedThing,
otherSpecializedThing,
nextSpecializedThing,
sendSomethingBack
)([req, res])
somePromiseWhichIsAFutureValue
.then(somethinWithThatValue)
.then(somethinWithThePreviousValue)
.then(somethinWithTheValueJustBeforeThis)
// If we pretend `pipe` is async
pipe(
somethinWithThatValue,
somethinWithThePreviousValue,
somethinWithTheValueJustBeforeThis
)(somePromiseWhichIsAFutureValue)
Whether you have realized it or not, you have been using function composition from the beginning. Imagine what you can do now that you recognize the pattern and can implement it in the code you write.