This post is Part 3 of a series about functional programming called Thinking in Ramda.
In Part 2, we talked about combining functions in various ways, ending up with the
pipe functions that allow us to apply a series of functions in a pipeline fashion.
In that post, we looked at simple pipelines of functions that only take one argument. But what if we want to use functions that take more than one argument?
For example, let’s say we have a collection of book objects and we want to find the titles of all of the books published in a given year. Let’s write that using only Ramda’s collection iteration functions:
It would be nice to combine the
map into a pipeline, but we don’t know how to do that yet because
map both take two arguments.
It would also be nice if we didn’t need to use an arrow function in
filter. Let’s tackle that problem first because it will teach us some things we can use to make the pipeline.
In Part 1 of this series, we talked about functions as first-class constructs. First-class functions can be passed as parameters to other functions and returned as results from other functions. We’ve been doing a lot of the former, but haven’t seen the latter yet.
Functions that take or return other functions are known as “higher-order functions”.
In the example above, we pass an arrow function to
book => publishedInYear(book, year), and we’d like to try to get rid of the arrow. In order to do that, we need a function that takes a book and returns true if the book was published in a given year. But we also need to pass along the year to make this flexible.
The way we can solve this is to change
publishedInYear into a function that returns another function. I’ll write it with full function syntax so you can see what’s going on, but then show you the shorter version using the arrow syntax:
With this new version of
publishedInYear, we can rewrite our
filter call, eliminating the arrow function:
Now, when we call
publishedInYear(year) is evaluated immediately, returning a function that takes a
book, which is exactly what
We could rewrite any multi-argument function this way if we wanted to, but we don’t own all of the functions we might want to use. Also, we might want to use some multi-argument functions in the usual way.
For example, if we had some other code that just wanted to check if a book was published in a given year, we’d like to say
publishedInYear(book, 2012), but we can’t do that any more. Instead, we have to say
publishedInYear(2012)(book). That’s less readable and more annoying.
Fortunately, Ramda provides two functions to help us out:
These two functions let us call any function with fewer arguments than it needs. They both return a new function that takes the missing arguments and then calls the original function once all of the arguments have been supplied.
The difference between
partialRight is whether the arguments we supply are the left-most or right-most arguments needed by the original function.
Let’s go back to our original example and use one of these functions instead of re-writing
publishedInYear. Since we want to supply only the year, and that is the right-most argument, we need to use
If we had originally written
publishedInYear to take
(year, book) instead of
(book, year), we would have used
partial instead of
Note that the arguments we supply to
partialRight must always be in an array, even if there’s only one of them. I can’t tell you how many times I’ve forgotten that and ended up with a confusing error message:
Having to use
partialRight everywhere gets verbose and tedious. But having to call multiple-argument functions as a series of single-argument functions is equally bad.
Fortunately, Ramda provides us with a solution:
Currying is another core concept in functional programming. Technically, a curried function is always a series of single-argument functions, which is what I was just complaining about. In pure functional languages, the syntax generally makes that look no different than calling a function with multiple arguments.
In Ramda, a curried function can be called with only a subset of its arguments, and it will return a new function that accepts the remaining arguments. If you call a curried function with all of its arguments, it will call just call the function.
You can think of a curried function as the best of both worlds: you can call it normally with all of its arguments and it will just work. Or you can call it with a subset of its arguments, and it will act as if you’d used
Note that this flexibility introduces a small performance hit, because
curry needs to figure out how the function was called and then determine what to do. In general, I only curry functions when I find I need to use
partial in more than one place.
Let’s take advantage of
curry with our
publishedInYear function. Note that
curry always works as if you had used
partial; there isn’t a
partialRight version. We’ll talk about that more below, but for now, we’re going to reverse the arguments to
publishedInYear so that the year comes first.
We can once again call
publishedInYear with just the year and get back a function that then takes a book and executes our original function. However, we can still call it normally as
publishedInYear(2012, book) without the annoying
)( syntax. The best of both worlds!
Notice that to make
curry work for us, we had to reverse the argument order. This is extremely common with functional programming, so almost every Ramda function is written so that the data to be operated on comes last.
You can think of the earlier parameters as configuration for the operation. So, for
year parameter is the configuration (what year are we looking for?) and the
book parameter is the data (where are we looking for it?).
We’ve already seen examples of this with the collection iteration functions. They all take the collection as the last argument because it makes this style of programming easier.
Arguments in the Wrong Order
What if we had left the argument order of
publishedInYear alone? How could we still take advantage of its curried nature?
Ramda provides a couple of options.
The first option is
flip takes a function of 2 or more arguments and returns a new function that takes the same arguments, but switches the order of the first two arguments. It is mostly used with two argument functions, but is more general than that.
flip, we can go back to the original argument order for
In most cases, I’d prefer to use the more convenient argument order, but if you need to use a function you don’t control,
flip is a helpful option.
The more general option is the “placeholder” argument (
What if we have a curried function of three arguments, and we want to supply the first and last arguments, leaving the middle one for later? We can use the placeholder for the middle argument:
You can also use the placeholder more than once in a call. For example, what if wanted to supply only the middle argument?
We can use the placeholder style instead of
flip if we like:
I find this version more readable, but if I needed to use the “flipped” version of
publishedInYear a lot, I might define a helper function using
flip, and then use the helper function everywhere. We’ll see some examples of this in future posts.
__ only works for curried functions, while
flip all work on any function. If you need to use
__ with a normal function, you can always wrap it with a call to
Let’s Make a Pipeline
Let’s see if we can now move our
map calls into a pipeline. Here’s the current state of the code, with the convenient argument order for
We learned about
compose in the last post, but we need one more piece of information so that we can take advantage of that learning.
The missing piece of information is this: almost every Ramda function is curried by default. This includes
filter(publishedInYear(year)) is perfectly legal and returns a new function that’s just waiting for us to pass the
books along later, as is
map(book => book.title).
And now we can write the pipeline:
Let’s take this one step further and reverse the arguments to
titlesForYear to match Ramda’s conventions of data-last. We can also curry the function to allow its use in later pipelines.
This post is probably the deepest one of this series. Partial application and currying can take some time and effort to wrap your head around. But once you “get” them, they introduce you to a very powerful way of transforming your data in a functional way.
They lead you to start building transformations by creating pipelines of small, simple building blocks.
In order to write code in a functional style, we need to start thinking “declaratively” instead of “imperatively”. To do that, we’re going to need to find ways of expressing the imperative constructs we’re used to in a functional way. Declarative Programming discusses these ideas.