In the previous two chapters we went through some of the basic paradigms in functional programming — pure functions and immutability. If you haven’t read them you can start from here.
In this article we will expand our knowledge of functions and their properties by laying the foundations of how they can be used together to compose the logic of your program.
The goal of functional programming is to build our programs using mainly small pure functions that can be assembled together as if you were combining small lego pieces.
Function composition is the act of combining multiple functions together in order to create more complex logical flows. By using this technique we can avoid iterative code structures that reads like a stream of operations and instead abstract away the complexity of the process by combining the instructions into a single function
Don’t be intimidated by the length of that last sentence. To put it simply — here we will start learning how to combine functions together.
From what we learned in one of the previous articles — functions should be small, pure and generate no side-effects. A function which adheres to those conditions is a pure function and pure functions are quite easy to compose.
The functions always return the same type and given the same arguments return the same value, which makes them predictable. And unlike movies, you want your functions to have a predictable outcome so you can easily use them in your codebase.
The precondition that we need to have in mind is that when we compose functions each one should take the output from the previous. We can’t just take some functions, stick them together and expect them to work — their input and output needs to match.
You can imagine function composition like a combination of pipes. The execution starts from the first (the right-most one) and whatever it outputs will be passed on to the second. The second function then takes that, does something with it and passes it on to the next function and so on.
Later in the article we will encounter another way to compose functions together, but for this particular example remember that execution starts from the inside out or from right to left — whichever is easier for you to remember
Now let’s get back to the preconditions that we need to have in mind when using function composition. If you have a function that takes a number as an argument you can’t really compose it with one that returns a string. Well, technically you can but you won’t get the desired result.
Another simple way to explain composition is that the result of consecutively calling the functions should be equal to the result of composing them together.
By proving that both approaches have the same result we are sure that we have composed our functions properly and they don’t rely on any extra input.
Also notice that we’re not just passing the inner function as an argument, this would be a Higher Order Function. The innermost function is actually being called with its arguments.
Whenever I am learning something new I like to consider where I could apply it and how it can be useful.
I had a case recently in which an API expected to receive a JSON payload formatted in a specific way. One way to do this is to implement the formatting logic in the code itself but this is not something that you really want. No matter what service or app you are building you don’t want the insides of your app to depend on something external.
So, I decided to implement an extra step before dispatching the payload to parse it and prepare it. The simplest approach here is to create a
parsePayload function that takes the object and returns a new one ready to be sent.
My worry was that the client API’s required format may change, so I decided to split up the operations in multiple small functions and be more flexible when it comes to modification in the future. Another big benefit of that is the clarity of the parsing process.
In this particular example we start from
filterEmpty. Don’t forget that with this particular way of composing execution is always from the inside out. After we filter out the empty properties, we return the object to the
formatValues function which will take this object and do what its name suggests. It returns the formatted object to
mapKeys which does some work on the keys and then it ends up being stringified so it can be sent.
Even though I try not to give mathematical explanations, function composition is a concept that is easily described with a mathematics example.
Imagine that you have some calculation
x + 3 and you want to multiply its result by
10 — you would write this as
(x + 3) * 10. Here’s how we can easily implement this using function composition
Looks quite intuitive right? It can be read out loud and it will make perfect sense what you are trying to do — sum numbers
5, then multiply by
10. This is the example that made function composition click for me and I believe it best illustrates how descriptive functional code can be.
For chaining to work, though, you need to be passing around the same object because you are not just calling a function, you are calling it on the object that is passed from the previous one. Therefore you need that object to include all the functions that you want to chain.
In a case in which you want to filter and map an array, chaining is the better and more readable approach. But in a scenario in which you expect the data type to change or you are implementing a complex flow that can’t be supported with the built in methods, composition can still benefit you without sacrificing readability.
There are many JS libraries built with functional programming in mind, but for this example we will be using Ramda. It is the perfect choice if you want to test the water and see how you will feel using a more functional approach to building your programs.
For simplicity let’s use an example with a few computational operations.
This example is pretty simple and it uses only three functions so it’s still understandable. However, if we need to compose five functions it will go out of control and become harder to read. We can always split the functions in different rows but we must be careful not to overdo it, especially when the count of the composed functions rises.
To avoid unnecessary confusion we can use Ramda’s
compose function. The benefit of using a library is that the syntax can be easily understood and looked up so colleagues that haven’t played around with functional programming can still understand what exactly is happening without relying on you directly.
The first difference that you can see here is that inside the compose call we are not providing the arguments for the first function. In fact compose will return a new function which we can then call. Also, bear in mind that functions created with composed are executed last to first — meaning that
sum will be ran first, followed by
square and then
addTen. If you want to have them run from first to last you can use the
pipe function that is provided by Ramda. The syntax is 100% the same with the difference being the order of execution.
Function composition is in the core of functional programming and will teach you the basics that you will need to create powerful functions and flows. What we’ve learned in this article barely scratches the surface of the topic but it serves the purpose of getting you more comfortable with manipulating functions
This is the moment in which the salesman says “But wait, there’s more!”. And there sure is more — by combining what we’ve learned in this article witha couple of other functional programming concepts — currying and partial application, we can create incredibly powerful abstractions and unlock the true potential of functions.
In the next article we will be looking into partial application and what is so special about it!