Thinking in Ramda: Combining Functions
This post is Part 2 of a series about functional programming called Thinking in Ramda.
In Part 1, I introduced Ramda and some of the basic ideas about functional programming, such as functions, pure functions, and immutability. I then suggested that a good place to start is with the collection-iteration functions such as
select, and friends.
Once you’ve gotten used to the idea of passing functions to other functions, you might start to find situations where you want to combine several functions together.
Ramda provides several functions for doing simple combinations. Let’s look at a few.
In the last post, we used
find to find the first even number in a list:
What if we wanted to find the first odd number instead. We could always write an
isOdd function and use it, but we know that any number that isn’t even is odd. Let’s reuse our
Ramda provides a higher-order function,
complement, that takes another function and returns a new function that returns
true when the original function returns a falsy value, and
false when the original function returns a truthy value.
Even better is to give the
complemented function its own name so it can be reused:
complement implements the same idea for functions as the
! (not) operator does for values.
Let’s say we’re working on a voting system. Given a person, we’d like to be able to determine if that person is eligible to vote. Based on our current knowledge, a person must be at least 18 years old and be a citizen in order to be able to vote. Someone is a citizen if they were born in the country or if they later became a citizen through naturalization.
What we’ve written above works, but Ramda provides a couple of handy functions to help us clean it up a bit.
both takes two other functions and returns a new function that returns
true if both functions return a truthy value when applied to the arguments and
either takes two other functions and returns a new function that returns
true if either function returns a truthy value when applied to the arguments and
Using these two functions, we can simplify
both implements the same idea for functions as the
&& (and) operator does for values, and
either implements that same idea for functions as the
|| (or) operator does for values.
Ramda also provides
anyPass that take an array of any number of functions. As their names suggest,
allPass works like
anyPass works like
Sometimes we want to apply several functions to some data in a pipeline fashion. For example, we might want to take two numbers, multiply them together, add one, and square the result. We could write it like this:
Notice how each operation is applied to the result of the previous one.
Ramda provides the
pipe function, which takes a list of one or more functions and returns a new function.
The new function takes the same number of arguments as the first function it is given. It then “pipes” those arguments through each function in the list. It applies the first function to the arguments, passes its result to the second function and so on. The result of the last function is the result of the
Note that all of the functions after the first must only take a single argument.
Knowing this, we can use
pipe to simplify our
When we call
pipe passes the
4 to the
multiply function, resulting in
12. It passes that
addOne, which returns
13. It then passes that
square, which returns
169, and that becomes the final result of
Another way we could have written our original
operate function is to inline all of the temporary variables:
That’s much more compact, but somewhat harder to read. In that form, however, it lends itself to be rewritten using Ramda’s
compose works exactly the same way as
pipe, except that it applies the functions in right-to-left order instead of left-to-right. Let’s write
This is exactly the same as
pipe above, but with the functions in the opposite order. In fact, Ramda’s
compose function is written in terms of
I always think of
compose this way:
compose(f, g)(value) is equivalent to
pipe, note that all of the functions except the last must only take a single argument.
compose or pipe?
I think that
pipe is probably the easiest to understand when coming from a more imperative background since you read the functions left-to-right. But
compose is a bit easier to translate to nested-function form as I showed above.
I haven’t yet developed a good rule for when I prefer
compose and when I prefer
pipe. Since they are essentially equivalent in Ramda, it probably doesn’t matter which one you choose. Just go with whichever one reads the best in your situation.
By combining several functions in specific ways, we can start to write more powerful functions.
You may have noticed that we mostly ignored the function arguments when we were combining functions. We only supply the arguments when we finally call the combined function.
This is common in functional programming, and we talk about that a lot more in the next post in this series, Partial Application. We also talk about how to combine functions that take more than one argument.