Much of functional programming is about composing functions together to handle our data in predictable ways. Simple functions neatly tied together create increasingly complex, yet dependable functions to build upon.
This blog post is part 1 of a multi-part series. In part 1, we will introduce the library Ramda and the concepts of composition, pointfree style, and functors through the simplest of examples.
Disclaimer: Please note that I am not an authority on functional programming by any means. I intend this little series solely as “introduction to concepts”. This guide is quite unconventional in the order of the content. I choose to focus on practical examples, leaving out essential theory such as typeclasses and function purity. If you thirst for more technical resources, I highly recommend http://github.com/MostlyAdequate/mostly-adequate-guide, or you can also check out this free ebook on Haskell: http://learnyouahaskell.com/.
Getting Our Feet Wet
Let’s say we have a person in a string format such as:
“Doc Emmett Brown”. Our task is to obtain the person’s first initial, so simply creating the string
“E” in this very case.
If you are reading this, you might already be keen to program with small functions that have single responsibilities. Let’s build on top of this idea from scratch with this starter code:
getFirstInitial is the main function. In it actually lies the essence of compositional programming (the function is nested). We build on top of the first two simple functions (that are trivial to test and debug) to create a slightly more complex function that we can rest assure is fundamentally sound! Easy does it.
Yet, you might agree that our nested function here doesn’t look all that friendly (although ES6 makes it quite tolerable with the arrow syntax). It’s a bit of an eye sore and not too appealing to work with, given how simple the logic actually is:
It’s not incomprehensible by any means, but complexity is our enemy so let’s nip it in the bud.
Let’s Compose with Ramda
We can do better by composing our functions together. Let’s import Ramda’s
pipe function and update our code:
firstInitial will first run through
getFirstName, which will pass the result to
getFirstLetter, which in turn gives us our final result!
The code is easier to grok and to maintain this way. We’re easily nesting or composing functions together. In fact, there is an equivalent function in Ramda called
compose and it does the exact same thing as
pipe, but the arguments go in the reverse direction, like so:
pipe and reading the code like normal English (left to right). Ramda does a lot more than composition (we’ll see more use cases in part 2).
Something else interesting is happening here with
compose. In our original
getFirstInitial function, we had a reference to
person in there (in fact we even had two), take a look:
With Ramda, the function definition is free of that pesky data reference. Isn’t it nicer on the eyes (and on the brain)? That’s called pointfree style.
You don’t have to tie your function to a “person” input variable (or rename that variable over time). It’s more encompassing this way: we could as well pass in a fictional character that fits the format of the string but isn’t necessarily a person (
“Smurf Papa Smurf”).
More Syntax Goodness
Note how you could drop another function in the middle of this composition easily, let’s say a
log function (if we had implemented one).
pipe(getFirstName, log, getFirstLetter)
Think about doing that with the original nested function. Couple times as long to edit? Here it is:
It’s rather ugly. Wait, did I get the parentheses just right?
The Real World
Let’s say our original input string could very well be
null. No data. What happens when we call
firstInitial(null)? It breaks! It begins as instructed with
getFirstName, goes in to try to perform
String.split on what turns out to be
null, and it errors out.
Alright, no biggie, we could sprinkle our app with null checks in our functions to avoid this unfortunate turn of events.
Now, when we try to get the firstInitial of a
null, we get
null back, which is fairly reasonable. More importantly, the app didn’t blow up on us.
But let’s say a new requirement later follows that dictates we also check for
undefined as well as
null! We have to go back and change all the instances of these null checks in the nitty-gritty of our building blocks. It would work but it’s not really ideal. We don’t wanna mess with our building blocks every couple of minutes.
Do it, But Maybe
How can we lift our invalid checks outside of each individual function, so we can deal with them in one place? The pattern is always the same as we see in the duplication of the code above: if the value is
null, don’t do anything and just return
if(name===null) return null;
The key abstraction here lies in wrapping our data in a container that can perform its own check. Then, we pass around this container instead of passing around the raw data.
It’s a bit of leap, but bear with me. Let’s make such a container, why don’t we?
We’ll call this container method
fmap (for functor map)It’s reminiscent of
Array.map in that it looks inside the data structure (the array or the container) and returns a new structure based on the original element(s).
We’ll create a container factory that produces these container objects for us. We’ll call our factory
Maybe, because the wrapped value may or may not exist (may or may not be
Let’s see this container concept in code:
Our Maybe factory takes in a value and returns a new object with that value as
val as well as a
fmap itself accepts a function
f as a parameter, checks
this.val and calculates a new value to wrap and return! It’s nice that it wraps it for us because then we can keep “fmapping” over it. Just like
Array.map returns you an array and you can
If the ES6/2015 is unfamiliar, here is an ES5 implementation of Maybe:
Interesting, isn’t it? Maybe actually returns a functor, which is loosely described as any data structure that can be mapped over. And “mapped over” simply means you are creating (or “projecting”) a new value based on the original one (with a function).
Fixing the building blocks
Now, we need to change
getFirstLetter to handle our
Maybe containers instead of plain strings (which in fact could be
null!) Let’s do some updating:
We’ve now delegated the
null check to the Maybe container! The great thing is that
Maybe is nice enough to provide its own interface (as we built it ourselves!) so it can act based on its own internals. It’s introspective. We just call its
fmap method passing in a function that we want to invoke on the internal value… as long as it‘s not gonna crash the app!
Let us recap our code, including a check for undefined.
Feel free to fork and edit this code at: http://codepen.io/collardeau/pen/JYpLEY
Now we can compose with more ease and assurance thanks to our new functor Maybe.
Stay tuned for part 2, where we shall learn about currying. Fun stuff ahead.
Update: Part 2 is now available.
Feedback is always appreciated. You can also follow me on twitter.
Thanks for reading!