In the beginning, computers were like dinosaurs: enormous in size, but tiny in computational ability.
This brain-the-size-of-a-walnut phenomenon is the reason that functional programming, although the first computer programming paradigm ever created, was eclipsed by the imperative and object-oriented paradigms. Quite simply, early computers had limited processing power and simply could not afford the fact that function calls are more expensive than simple loops. And so for decades functional programming largely lived on in academia, while the rest of us got to wrestle with side effects and (shudder) state.
So What Is Functional Programming, Anyway?
Functional programming is a paradigm where functions are king, rather than objects and assignments. It is also a style of coding, of organizing and then writing your code, that is a different way of thinking about how to approach a task or solve a problem.
In functional programming, everything is expressed in terms of functions. (Big surprise, huh?). It is extremely explicit: taking input scoped only to that specific function, and computing and returning output. That’s it. Whereas imperative programming takes a “first take this, then do that” style, promiscuously grabbing variables from all kinds of scope — which can lead to unintended side effects. And in object-oriented you programming, you get the fun of ‘this’ bindings.
Typical imperative function:
A more functional way of doing the same thing is to define a function that takes an input, name, and returns a direct output:
Pure Functions, and a Little on the Side (Effects)
A pure function does nothing except take its input, use that and only that to compute an output, and then return that output. Mic drop.
In our above simple functional code example, that is not a pure function: it’s using a globally defined variable to calculate its output. Involving something from outside the function in what the function itself does is most impure. (By the way, technically speaking console.log() also fails the purity test, but we forgive that because it’s so useful, and also unlikely to actually screw up our code by producing side effects).
Functions are First-Class Citizens. Higher-Order Functions Especially So.
Functions that take other functions as inputs are called higher order functions. In the sample code below, note how the vote() function returns another function as output. We can access both of these by assigning the outer, higher-order function to a variable — but our inputs are controlled entirely within the nested functions, and therefore immutable. They are not in global scope:
The reason that our inputs for ‘election’ are not in global scope has to do with closure: the function defined in the closure remembers the code block in which it was created, and thus will take only inputs also scoped to that same block.
Take some time to sit with this. Wrapping your head around the concept of functions returning functions (returning functions) is the key to programming in a functional style. We need higher order functions to be able to avoid tricks we are used to using form other paradigms. for example…
Instead, in functional style, we use higher order functions like map(), reduce() and filter() — which accept as arguments not only the object or array you want to access, but also any function you’d like to then apply to them.
Ladies and gentlemen, I give you the Map Reduce Sandwich:
In functional style, though, we take the map() method and pass it the array of ingredients and the function chopped(), and it returns a new list where everything has been chopped. Yum:
When comparing both, it’s clear that with functional programming our code is composable, cleaner, and written to match the problem we want to solve. Plus, no risk of weird random ingredients crashing our sandwich!
After everything is chopped, we can then use the reduce() method to combine all the items into our delicious sandwich.
And, if you happen to hate tomatoes, there’s the filter() method to keep them out by making sure that only items that are NOT tomatoes get through the filter.
Now Don’t Go Changing.
Functional programming is all about avoiding mutability — i.e., don’t go changing objects in place. (We love them just the way they are). Immutable means data that cannot be altered once in place; we can set it and forget it, and it will never change.
Let’s look at an example. First, mutation (bad!):
We have a ‘snacks’ variable holding tots, quesadilla, and ham — and then we realize that actually, ham should be eggs, so let’s replace index position 2 in ‘snacks’ with ‘eggs.’ This means the ‘snacks’ variable has been changed: i.e., we have changed in place what was stored in the variable. Yep. We mutated the heck out of that data set.
Functional programming avoids changing data in place, inside an object or array, because this can potentially cause a lot of unintended consequences. In fact, it’s quite often the source of serious trouble in object-oriented programming. Because, if you unintentionally change data, the lunch you thought you were dealing with — tots, a quesadilla, some ham — is more like a breakfast, because ham got swapped for eggs when earlier in your code you replaced that array element. This can introduce major head-clutching bugs which are going to be very hard to track down. Think about it: at one point in your code, ‘snacks’ is the right thing, in another ‘snacks’ is the wrong thing — but they’re both ‘snacks.’
That is the beauty of immutable data in functional programming: keeping track of your snacks:
Taking a functional programming approach by using map(), instead of changing snacks in place we return a whole new array, newSnacks, containing the changed item. This works by feeding a function into map() (<— higher-order functions accept functions as arguments!) which looks at each snack in the ‘list’ (array). If it’s ham, the function returns eggs instead. Otherwise, it returns the original snack item. Once we assign this newSnacks variable, it holds the tasty treats we expect, while our original snacks list has not changed at all.
Yes, yes, I know it seems trivial… but I promise that data immutability will help you avoid so many headaches. And crying in front of your computer.
We’ve Only Just Begun.
The only downside to keep in mind is, once a codebase gets seriously large, it’s possible to start having efficiency issues: function calls are more expensive than those dumbass simple loops… Expensive, but usually worth it.