Functional JS #2: Functions. Duh!
This is a second part of the “Functional JS” series. Go to the beginning of the series here.
Now that we know why learning functional programming practices can help you become a better programmer, let’s jump into the fun stuff.
In this part, we will focus on the vocabulary and basic concepts connected with functional programming.
Sadly, there won’t be a lot of code involved. On the bright side, once we understand the terminology, we will be able to discuss more complex subjects comfortably.
As you can imagine, the most important thing in functional programming is, well, a function.
We all know a function when we see one. It’s basically an organised (and named, for the most part) piece of code that does stuff.
When it comes to FP, though, we are interested in a specific point of view on what a function is: a mathematical one.
Math… not even once?
Even though we won’t have to deal with algebra ourselves, we need to acknowledge that functional programming is deeply rooted in maths.
And in simple, mathematical terms, a function is a machine that produces an output given an input.
The one interesting thing is that there can only be one output for a given input. Which means, if we provide the function with the same input, we expect it to always do the same exact thing, and return the same value.
This sounds trivial, but it’s actually a strong requirement. This mathematical definition has significant consequences:
- A function can not depend on anything except its input (arguments)
- A function has to return a single value
- A function needs to be deterministic (can’t use random values, etc.)
Functions that meet those criteria are called pure functions in programming, and they are crucial to the functional paradigm.
coin function is not pure, because it doesn't always produce the same result given the same (empty) input - it's not deterministic.
uppercaseName function is not pure, because it depends on a variable that's out of its control. We can't be sure it will always produce the same result given the same arguments.
happyBirthday function is not pure, because in addition to accessing out-of-control variables, it does not return anything.
calculatePrice function is pure. It doesn't use any variables out of its control, is deterministic, and we can confidently say that it will always return the same result for the same combination of input arguments.
Why does it all matter? There are a couple of reasons why pure functions are better than impure ones:
- They are easier to read
In order to understand what a function does, you only need to read its body.
- They are easier to reason about
There’s no need to look for external dependencies, the context in which the function is called, etc. None of this matters for pure functions.
- They are easier to test
If you want to test a function that is pure, you only need to call it with some arguments and see if the result is what you wanted it to be. No complicated setup required.
- They can be more performant
If we know that for a given input, the function will always produce the same output, we can cache (memoize) the result so we don’t have to recalculate it when the function is called again.
Using pure functions makes your code more maintainable — because it makes it easier to manage side effects. In the next parts we will learn what side effects are and why, sadly, computer programs can’t be pure functions “all the way down”.
Now that we know what pure functions are, let’s focus on the next function-related term: first-class functions.
A “first-class function” is, unlike the “pure function”, not a practical concept that’s useful on a daily basis. It does, however, come up when thinking about characteristics of programming languages.
You can say a programming language has “first-class functions”, if functions can be used just like any other values, i.e.:
- they can be passed around,
- they can be assigned to variables,
- they can be stored in more complex data structures, like arrays or objects.
Well, passing functions in and out of functions is a common practice in functional programming — and a very powerful one. Which brings us to…
Functions that “operate” on other functions are called higher-order functions. By operate, we mean they can either (or both):
- take other functions as arguments,
- return other functions.
Array.prototype.map function in the standard library. It takes a function and applies it to every element in the array:
When it comes to returning functions from other functions, here’s an (a bit contrived, I have to admit) example:
As you can see, these functions (
makeGreeter) don't accept/return regular values as we know them. They operate on functions.
You may already be familiar with some of the higher-order functions, like:
- … and others.
Functional programming is all about composing small, reusable, and generic functions into more complex ones. Given that, you can expect us to be discussing a lot of different higher-order functions in the posts to follow.
For now, this is all of the basic function-related terms we will need to start our journey.
Next time, we will focus on state in functional programming — how to manage it, how to avoid problems with it, etc. We’ve already brushed the surface of this (when discussing pure functions), but there’s much more to it!
We’ve already learned a lot, and I hope you’re as excited for the next part as I am!