This blog post is part 2 of a multi-part series. In part 1, we introduced the functional 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 essential theory such as typeclasses and function purity until late in the game. 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/.
Our task from part 1, for the purpose of illustration, was to grab the first initial of a user in a string format such as
“Doc Emmett Brown”, simply creating the string
“E’ in this case.
We were left with the following code. (To make it more accessible, I’ve rewritten it to keep only const and the arrow function from ES2015):
Composition is our bread and butter. We want to mix and match simple functions to make complex programs… not so complex for us to scale and modify!
In this post, we will discover the joys of curry which will allow us to create more intuitive function compositions. We will also dig deeper into Ramda as a tool to fit our eager functional mindset.
Let’s Go With It
You might have been somewhat saddened when we changed our building blocks to handle maybes. I wouldn’t blame you. Wasn’t it better, clearer, originally? Take a look:
It sure makes me a little teary-eyed. In fact, we’re repeating the same pattern in both cases: taking a maybe “thing” and “fmapping” over it (the goal being to keep our null check in one single place). Wouldn’t it be grand if we could factor out this functor logic?
Perhaps we’d have something like this (incomplete) code:
We’ve created a new
map function that takes a function and a functor, and applies that function to the value inside the functor with via
fmap. Our building blocks are kept clear and to the point, and we are starting to feel better about life.
But darn, how do we pipe this together now? Have we actually created a quagmire for ourselves? Let’s take a step back and see how we might do this in standalone JS:
OK, so we use our shiny new
map to get a (maybe) first name, and then we map that result to get the (maybe) first letter that we are after.
The root problem here is that we can’t use our beloved
R.pipe() to compose functions that take more than one parameter (and our
map function takes two). That’s because the output of one function is the input of the next one in the composition. So, how could we return two separate values from a single function? We can’t!
(And we won’t bother trying the ugliness of returning an object with multiple keys or an array to represent two values).
Curry Me Some New Functions
We need to prep our map function so it takes a single parameter at the time of composition. We’d want that single param to be the
maybeName, as that is the only thing that is unknown, the initial input, before we run the composition.
This is exactly what currying will help us do! Ta-da: here we are introduced to a functional programming concept: Curry.
After you curry a function, you can then “partially apply” it. That means you simply pass it fewer parameters than it expects, and it returns a new function that expects the rest of the parameters, until all parameters are met, and only then will you get the output result.
Yes, you might have guessed it: Ramda provides such a
curry function. Let’s look at the quintessential example, making an
add10 function based on an original
That’s all there is to it. We declare
add to take two parameters. We curry it. We partially apply the new curried function with one parameter to create a new function
add10 takes one param and returns a result. Done.
Let’s actually dive in a little deeper and write our own little implementation of curry. Homemade curry! (Gee, I had promised myself I wouldn’t make any food puns).
As a side note: how does the inner function of
addCurried keep a reference to the
Ramda helps a lot because our version only handles two parameters, while Ramda (or lodash-fp)’s curry will handle any number of parameters for us. And, not to mention, it’s a lot cleaner: just R.curry() any function our heart desire.
So, What About Our Code?
So now we have the solution in our mind, let’s return to the problem at hand! Check it out:
It’s worth noting that I was a bit sneaky about the order of the parameters when I first created the
map function. For the currying and composition to work,
map has to take the function we want to run first, and the data structure second (the maybe “thing”). It makes sense, but you have to be mindful of it.
This parameter ordering is often the case in functional programming: the data comes last, which also allows for the pointfree style that we saw before. Interestingly, underscore does it backwards, so as popular as it is, it doesn’t lend itself well to functional programming!
Because we’ve kept our function “pure” (some input always returns the same output and there are no side-effects), we can rearrange the composition and avoid “opening the container twice” (using mapCurried twice, which is not only an eye sore but also inefficient).
So let’s rewrite this once more. And while we’re at it, let’s actually make
map curried by default! And hell, we’ll also break down
getFirstName into two parts (adding a
getWords method) to truly keep all our responsibility separate, take a look:
Hey now, that looks pretty good. We simply use composition (
pipe) inside our pre-curried
map function! If we have a
Maybe(null) as input, the whole thing is circumvented right off the bat, and we return
Maybe(null) directly. If the
Maybe functor has data inside it, we run though each function in a normal pipe fashion. We are generally becoming more versatile with function composition.
Now, we know about the power of currying, which is handy in its own right (even outside of pure functional programming), but it also helps us keep our building blocks neat and to the point, that is, they are only concerned with their core functionality and not with functor logic.
We’ll see this is quite powerful in the next post because we can mix and match different functors in the compose chain.
Curry By Default
Now is a good time to mention that Ramda has heaps of functions available for us to use (similar to lodash or underscore)… and they are all curried by default (as are all functions in Haskell)!
Let’s see an example for good measure, using indexOf:
With Ramda’s curried version of
indexOf we can easily build a new function that always looks for the second index of an array. Sounds pretty good, especially if you do the same lookup multiple times.
What does that mean for us? We can easily rewrite our building blocks to be pointfree: without any mention of our particular data: no
“user”. (I’ve actually inadvertently swapped these names throughout this series; naming things is tricky). So let’s use Ramda for our building blocks:
Voila. For instance, Ramda’s
split actually expects two parameters (the separator, then the array). We only feed it the first one, so we have a new function who’s sole purpose is to separate words, and is waiting for data to work on. Perfect for our composition, as it only takes one param now.
Now, all our building blocks are native Ramda functions. So, why even wrap them in our own functions. Let’s use them straight in the
pipe and recap our code:
And that’s it for part 2! Now we have curry and more Ramda in our arsenal!
Please, feel free to check out or fork this code on CodePen.
In part 3, we will refactor the
Maybe factory to be more memory efficient. We will then introduce new functors.
Maybe isn’t enough for us serious programmers! We want to be able to handle proper validation beyond wrapping a
null in our composition. We want to extract useful messages if something goes awry. Stay tuned!
Update: Part 3 is now available.
Any feedback or criticism is most appreciated.
You can also follow me on twitter.
Thanks for reading!