I would love your feedback if you have any on what other topics you'd like to see - you can reach me on twitter: @harryblucas
This is another one of those terms that sounds fancy, but really isn't.
Function composition is process of combining two or more functions together into a single function, that produces the same result as if the functions were called independently and the result was passed from one to another.
To break it down - we take two (or more) functions, and pass the result of calling the first function with the supplied argument to the second function and so forth.
const func3 = (value) => func2(func1(value))
To simplify it, you can think of a function as an individual Lego block.
Function composition is then the process of combining one or more of these Lego blocks in to a larger structure.
For an example, let's pretend we want to take a string, and count how many words start with the letter
First we create our individual Lego blocks (functions) that we are going to use to build the bigger block (function).
Which in code would be defined as:
const split = (string) => string.split(" ") const downCase = (list) => list.map((s) => s.toLowerCase()) const countS = (list) => list.reduce((count, word) => word === 's' ? count + 1 : count, 0)
Since these blocks are all the same shapes and have matching side - we can combine them together 💪
To do this in code:
const wordsStartWithS = (string) => countS(downcase(split(string)))
To read a function composed like this, you read it inside out.
However this isn't as clear as it could be, so instead to make it a bit clearer, what a lot of functional programmers will do is introduce a
pipe function, which is super simple and makes the above, even clearer. To read an example of what a pipe function is you can click here.
const pipe = (...fns) => (initValue) => fns.reduce((val, func) => func(val), initValue)
Then we would use it like this:
const wordsStartWithS = (string) => pipe( split, downcase, countS )(string)
Which is now a very readable function declaration that reads like a recipe when cooking (split then downcase then count), all without a variable assignment.
Again just to reiterate what we've done here is combine multiple, smaller functions, into a single larger function.
Now of course this isn't the most robust example, but it is a start for getting your head around exactly what function composition is.
You can use function composition to compose your entire application together. You can take your smallest functions and combine them together into larger business cases, you can then combine these functions together with other business cases to build a use case - you get the picture - it's just functions all the way down.
The great thing about adopting more of a function composition mindset is that it forces you to break apart your functions into their smallest component pieces.
This promotes composability, reuse and is in my opinion much easier to read.
For example, if we wrote this the traditional way:
const wordsStartWithS = (string) => string.split(" ").map((s) => s.toLowerCase).reduce((count, word) => word === "s" ? count + 1 : count, 0)
Whilst we have gained the benefit of having it all in one line, we have:
- Made it harder to read (lots of extra noise)
- Harder for the reader to grok intent (they have to parse every single method call to figure out what it's doing)
- Removed any possibility of reusing those component parts (which leads