Functional JS from Α to Ω: forEach
[Α] Using forEach as a diving board to understand Functional Programming
We control complexity by building abstractions that hide details when appropriate.
– SICP, 1979
Do you want to learn that power?
A Simple List Iteration
Consider the following data: a list of strings.
We learned how to do that since day one of our programming journey.
For us, it looks like very intelligible code. But is it really?
It’s an imperative approach, since it describes how the computer should iterate, not how humans think about iteration.
It’s not as imperative as an
if combined with a
goto, but still.
And this code contains several other inherent defects:
- It’s not reusable / not DRY (we have to rewrite all of this for every similar case).
- Not decomposable, re-composable, since it’s a block of procedural code
- Not easily maintainable: typos can hide here, and what if something changes the value of i during the loop? It could lead to bugs.
- Not very scalable, since nested
forloops drive programmers crazy
Thanks to coder-friendly ES6, we now have some syntactic sugar to write it without declaring an extra variable as counter.
OK, it’s more declarative: a non-programmer might understand this code, just reading it left to right.
A little more maintainable and scalable too… but for a few levels of complexity only.
Old but Gold
forEach is not a native control structure, nor a reserved keyword. It’s an array method, executable through any Array, even an empty one.
The previous example will lead to an error, because
forEach takes one argument¹.
But not a simple, random argument: it takes a function as an argument.
Remember that in JS, functions are first-class citizens.
This doesn’t mean functions are something special — in fact, it’s the opposite. In JS, functions are like every other value (objects, or primitives boolean, number & string): we can put them in variables, combine them, pass them as arguments, return them, etc. (In Haskell, even operators like
+ can be passed as arguments!)
forEach is what functional programmers call a higher-order function. Nothing complicated either; it’s just a function responsible for running or returning other functions.
forEach is not magic; it takes a declared function (not executed!), but with the following signature:
(any) -> void². And it will apply this given function successively to every element in the array.
any argument stands to replace the current element in the iteration, and
void tell us that this function shouldn’t return anything.
The previous code will simply pop up an alert saying “hi <language>” for each language in our array. So now we can use what we know to solve our problem.
console.log a function? Yes.
console.log have an
(any) -> void signature? Kind of.³
So what’s the difference between
Can’t see any difference: the wrapping function is just noise pollution, so …⁴
Oh … yes … sounds weird, but … clearer in some way.
Is it… that? Are we doing real FP now?
No, it’s just the #A of this “from A to Ω” collection. The real FP world is very strict because it tends to only allow provable programs and controlled side effects. And if you want to reach this ideal, you have to follow some rules.
Pure Function and Immutability
If you don’t understand everything that follows, don’t panic. We will dive into all of this, episode by episode. Just try to familiarize yourself with the contents.
Do you remember the signature of the function that we can pass to the
forEach method? It’s a function that returns nothing (so-called “undefined” in JS), or
void in a typed world. And
forEach itself is a method that returns
This is the obvious symptom of functions with side effects. If it returns nothing, it should do something that affects the program elsewhere, before the return statement. Like changing some value of a global variable, or altering the array itself.
Something that one day we’ll forget. And it will surely lead to bugs. In conclusion, it’s not under control, while Functional Programmers are control freaks. They always want to know what they’re doing and even why.
forEach is way more composable than a
of since it takes a custom function, but still deeply chained to Array. We can’t easily pass the
forEach itself to another function.
Anyway, composability and scalability are not achieved either.
So we already know that we have to define our own
forEach if we really want to enter the FP world.
Let’s begin simply by hiding implementation details in a brand new function:
But…! It’s fully imperative! You lied to us, traitor!
Mmmh, I never said how the native
Array.prototype.forEach JS method was implemented.
Yes, maybe I lied by omission. But we now have our own
forEach, and once it’s fully written, we’ll never dive into this function anymore. It will be the most perfect
forEach ever, and we’ll use it, as it is, in 30 years. Functions are just about hiding imperative details to give us a declarative tool.
No one expects you to forget all the knowledge you already learned in computer science, quite the opposite!
Even so, there is a way to iterate without using a
for. It’s called recursion, but that’s for later, and without TCO it can lead to new problems.⁵
Here’s an example if you want to get your first sight of it.
Let’s finish our real-world
forEach, to make a pure function. We will discover what exactly it is later. But for now, just remember that a pure function:
- should return something
- should not modify anything outside its scope, even the references given as arguments⁶
forEach seems really better to my eyes, used to find and destroy side effects.
But, it clearly doesn’t solve our maintainability/scalability problem entirely.
Recently, a friend wanted to argue about FP. He understood everything about the requirements of pure functions (control, TDD, reusability, etc.), but he regretted the requirement of immutability. Let me ask a question: if all functions are pure and your program is only made of those functions, when will you face any mutability needs? Never. Pure function, aka referential transparency, is just the same concept as immutability, seen from another angle.
So let’s invoke another useful tool from the FP world: currying.
Because of what we said about functions as first-class citizens (just like any other values), we know that a function can return a function, enclosing parent variables values in it.
So we can finally write a very reusable and composable
Note the new order of the
func, that will be partially applied
- then the array
This new order allows us to create and reuse
logEach or even
doubleAndLogEach. In the future, it will help us to compose/pipe
forEach with other functions.
forEach still has a big flaw: an array containing objects still exposed to mutations.
forEach (for that particular problem, using an alternative like TypeScript or Elm could be the only choice).
So we didn’t reach the promised perfection: can you propose your perfect
forEach, in the comments?
A last word, just to get you angry after all of this. You may have discovered it reading this article: the concept of
forEach itself is not FP oriented. It does imply side effects in its own definition. That’s why our holy trinity will not contain
forEach, but only
reduce, as you will see in the next articles.
Thank you for your time!
[¹]: In fact, it can optionally take a second argument: a contextual value for
[²]: The real one is more like
(any, number, any) -> void, where the number is implicitly the current index, and where
any is the calling array. Those other args can be useful if you need to know if the current element is last in the array, for example.
[³]: Except that the real
console.log signature accepts an unlimited number of optional args, separated by commas, and then returns false
(any, …any) -> false.
[⁴]: In fact, because of the signature difference, console.log will just show more (see [²] and [³] and jump to conclusion ^^).
[⁵]: Tail Call Optimization is a prerequisite to use recursion in place of