An Emoji-Lover’s Guide to Functional Programming
Pipeline Stormy Clouds into Sunny Clouds
An advanced use of array map, array reduce, and functional composition using pipe
Learn functional programming with Emojis and JavaScript. The code examples should be simple enough to figure out without any prior knowledge, but I can imagine it’ll look a little strange. Also, JavaScript doesn’t actually allow emojis as JavaScript variable names. For this reason, these code examples won’t run without modification.
We start with our add
and subtract
methods from last time:
add = additive => item => item + additive
subtract = subtractor => item => item - subtractor
We talked about using multiple map
statements to change our stormy clouds to sunny clouds:
[⛅, 🌦️] = (
[🌩️, ⛈️️]
.map(subtract(⚡))
.map(add(☀️))
)
But did you know we could remove a map
by composing add and subtract:
addSun = add(☀️)
removeLightning = subtract(⚡)[⛅, 🌦️] = (
[🌩️, ⛈️️]
.map(item => (
removeLightning(
addSun(item)
)
)
)
Why would we do this? We could’ve just kept our two map
statements and everyone would’ve been happy! Typically, that’s what you would do, but in some cases, you might need a different way to chain using either a compose
or pipe
. They perform the exact same functionality, except pipe
takes parameters in reverse order similar to Polish notation:
compose = (second, first, item) => second(first(item))pipe = (item, first, second) => second(first(item))
Normally these return functions rather than taking item
directly:
compose = (second, first) => item => second(first(item))pipe = (first, second) => item => second(first(item))
We’ll use pipe
for our examples since it reduces the complexity by reading left-to-right.
Let’s assume we need to micro-optimize our code and two maps is too many. If we wanna get it down to one map, we’ll need to compose our functions. As we saw in the previous example, that can get ugly fast! Writing a simplified pipe
function, we can accomplish our goal and keep the code clean:
pipe = (first, second) => item => second(first(item))[⛅, 🌦️] = [🌩️, ⛈️️].map(pipe(subtract(⚡), add(☀️)))
Now that we’ve figured out the basics of pipe
, we’ll need to write a real one. That means we have to be able to give pipe
an infinite number of functions for it to call in-order.
Functionally, this is simple using reduce
, but without understanding the basics, it’s pretty tough to reason about. Let’s look at this procedurally:
item = nullupdateItem = change => {
item = change(item)
}pipe = (...functions) => {
[item] = functions.splice(0, 1)
for(let i = 0, l = functions.length; i < l; i++) {
updateItem(functions[i])
}
return item
}⛅ = pipe(☁️, subtract(⚡), add(☀️))
In this example, there’s no closure. We’re directly passing in the cloud emoji first then adding the rest of our functions as separate parameters.
First thing we do in our procedural pipe
is remove the first argument. That’s going to be our item
. We’ll loop the rest of the functions, calling them one by one and updating item
to the latest value, until we’re done with the loop where we’ll return the modified item
. Pretty messy.
This code has a lot of mutations. We’re creating item
and then changing the value of item
each time we loop somewhere else in the code, and updateItem
is creating a side-effect.
There’s another problem here too: splice
. This function mutates the array; pulling out a set of values and leaving the original array with the remaining values. Sure it makes sense right now when the code is small, but as you add in more pieces, it will be much harder to figure out the state of item
and the functions
array.
Mutations and side-effects are no longer needed with functional programming. By removing them, we end up writing pure functions which are far easier to unit test and multithread. Right now, the possibility of bugs is high. With pure functions, that possibility is greatly reduced.
Here’s the same pipe
using immutability, functional composition, and reduce
.
pipeReducer = (item, change) => change(item)pipe = (...functions) => {
[startingItem] = functions
return (
functions
.slice(1)
.reduce(pipeReducer, startingItem)
)
}⛅ = pipe(🌩️, subtract(⚡), add(☀️))
Notice how we use slice
(an immutable version of splice
) so we don’t have to mutate our array of functions. The very first thing we do is use deconstruction to pull out startingItem
from functions
. Next, we use slice
again, this time to take everything but the first item. We can then run reduce
on our smaller array and call change(item)
on one return value after another.
While reduce
loops through each value in the array like map
, each iteration gets both the previous value and the current one. The major difference is it ultimately returns a single value of any type. Also, if you don’t give reduce
an initial value, it will default to using the first value in the array. That means we can simplify this even more:
pipeReducer = (item, change) => change(item)pipe = (...functions) => functions.reduce(pipeReducer)⛅ = pipe(🌩️, subtract(⚡), add(☀️))
This is looking really good now! Just like add
and subtract
, we’ll want to use closures so pipe
can generate new functions. There are a few ways to write pipe. One is to explicitly pass our starting item as the initial value for reduce
:
pipe = (...functions) => startingItem => (
functions
.reduce(pipeReducer, startingItem)
)
The other way is to add our startingItem
to an array and concat
our pipelined functions:
pipe = (...functions) => startingItem => (
[startingItem]
.concat(functions)
.reduce(pipeReducer)
)
It doesn’t matter which you choose. With pure functions, as long as the same inputs always get the same output, you can always refactor the inner function as much as you like. Using either of these methods, we’ll end up with the same result:
changeStormyToSunny = pipe(subtract(⚡), add(☀️))⛅ = changeStormyToSunny(☁️)
As a major benefit, we’ve suddenly got reusable weather changing code! Using our knowledge of closures, function generators, and pipe
, we can refactor the double map
into a single, readable map
.
add = additive => item => item + additive
subtract = subtractor => item => item - subtractorpipeReducer = (item, change) => change(item)
pipe = (...functions) => startingItem => (
functions
.reduce(pipeReducer, startingItem)
)changeStormyToSunny = pipe(subtract(⚡), add(☀️))[⛅, 🌦️] = [🌩️, ⛈️️].map(changeStormyToSunny)
FEEL THE SIMPLICITY!
Click here to go to Part 3!
More Reads
If you’ve got an interest in more topics related to functional programming, you should checkout my other articles: