Function composition lets us combine multiple functions into steps that transform our data as it flows through them. It’s like an assembly line where each step alters the data in some way. Technically you don’t need to use functional code to create a composable function, but when you do, the result is clean, elegant, easily reasoned, and beautiful code.
An Imperative Approach
Let’s say I have an
article object that contains three keys:
concepts. Each key contains an array of objects that contains several keys including
text. I want to extract the
text from each item if the
relevance is greater than 0.7. Then I need to dedupe them using a case insensitive comparison. Also, entities sometimes contain
disambiguated forms of the text, so I need to use that if available. If I were to do this with imperative programming, it might look like this:
There are plenty of other ways to accomplish this, such as creating a deduping function. The code above isn’t overly complicated. We easily handled the
disambiguated condition with a simple ternary operator and our code is fairly easy to reason about. However, that is a lot of code for such a simple task. We need to tell the system to LOOP over each array and then give them specific instructions as to what to do during each iteration. During each iteration we are modifying a variable, which in a small script like this isn’t a big problem, but side effects in large scripts can be very difficult (and frustrating) to debug.
A More Functional Approach
Now look at the example below. I’m using the Ramda.js functional programming library so I don’t have to write my own compose function. Ramda.js also has a lot of other handy functions that make life a lot easier.
Isn’t this much cleaner and more elegant? If another programmer looks as this, they can easily reason about this code and understand exactly what it is doing:
- Line 3: This is a composed function, so it processes in reverse order (you could also use R.pipe if you wanted it to process the other way)
- Line 9: Pass an empty array into the composed function
- Line 8: Filter
articles.concepts, including only items where relevance is greater than 0.7 and then concatenate that with the empty array passed in
- Line 7: Do the same thing as line 8 with
- Line 6: Do the same thing as line 8 with
- Line 5: Map over the array and return just the
textor the disambiguated
nameif it exists
- Line 4: Use Ramda’s uniqBy function to create a deduped array by comparing the lowercase versions of each item in the array
This code is so simple that it took me less than 2 minutes to write. It is also pointfree, meaning that there are no intermediate variables that are manipulated. The data just flows through and returns a final result with no side effects. This code tells the interpreter WHAT to do, not HOW to do it like our imperative example.
Become a Better Programmer
There is a bit of a learning curve to understanding functional programming, but once you get it, you’ll never go back to your old ways. If you’re curious about the status of your data at any step in the composition, you can simply use Ramda’s tap method to log the current state. If you need to create more complex transformations, you can write your own composable functions and add them as additional steps.
Being able to quickly write easily-reasoned, bulletproof code is paramount to your efficiency and efficacy as a programmer. Stitching together small, independently-testable, pure functions will not only take you to the next level as a developer, but will help you to create beautifully written code that your team members and future developers will love you for.
Originally published at www.jeremydaly.com on September 14, 2017.