July 4, 2018

Currying & Function Composition

A crash course in implementing and using two of the most fundamental functional programming techniques.

Currying & Function Composition

A crash course in implementing and using two of the most fundamental functional programming techniques.

I want to demonstrate two foundational techniques you need to know to practice functional programming: function composition and curried functions. We'll learn what these terms mean, how to implement them, and the advantages of using them.

These techniques are applicable in any programming language where functions are first-class values, meaning they can be passed around like any primitive value. For the purposes of this post, we will be implementing these patterns in ECMAScript 2018 (JavaScript).

Function Composition

Function composition sounds fancy, but it's really not. It's actually just a way to automate something we all do manually anyway.

Imagine we have these three functions:

const getDescription = (obj) => obj.description
const truncate = (str) => str.slice(0, 100)
const appendEllipsis = (str) => `${str}...`

And we want to get the description from an item and format it to be 100 characters max and with a trailing ellipsis.

Without function composition, we would do this:

const description = getDescription(item)
const truncatedDescription = truncate(description)
const descriptionWithEllipsis = appendEllipsis(truncatedDescription)

We set the return value of some function to a variable, and then turn around and pass that variable into the next function, and so on. This is a really common practice for most programmers. The only purpose of those variables is to pass them to the next function, so really we could just do this:

appendEllipsis(truncate(getDescription(item))))

But that's not very easy to read is it? That's why (I hope) no one does that. But is using temporary variables really the solution? Must we choose between being writing verbose readable code or concise unreadable code? In fact, no; we don't. We're programmers. We can automate the process of passing output to input.

We can do that by writing a function called composeLTR (read: compose left-to-right) [1], which takes any number of functions as arguments, and then returns a new function that will pass its arguments to the first function, then pass the return value to the next function, and so on.

Here's the sales pitch. Using composeLTR would transform our code into this:

const extractFormattedDescription = composeLTR(
    getDescription,
    truncate,
    appendEllipsis
)

extractFormattedDescription(item) // => 'This is a truncated description...'

No more setting return values on temporary values and it's super readable. That's great, because I hate having to come up with unique names for everything and and I hate hard-to-read code. Who's with me?

Implementing It

Implementing composeLTR isn't much work, but it can make your head spin a little bit at first. It looks like this:

const composeLTR = (...fns) => fns.reduce((first, second) => {
    return (...args) => second(first(...args))
})

All it does is exactly what we said: it takes functions and returns a functions that automates the passing of input and output between those functions.

Curried Functions

No, we're not cooking functions. We're talking about a concept popularized in the worlds of combinatorial logic and computer science by Mr. Haskell Curry. I'd rather just show you a curried function than try to explain it.

Say you have function replaceAtIdx, that takes index, value, and array, and returns a copy of the array with the value at index set to value. It might look like this:

const replaceAtIdx = (index, value, array) => {
    return array.map((val, idx) => idx === index ? value : val)
}

replaceAtIdx(2, 'hi!', ['zero', 'one', 'two']) // => ['zero', 'one', 'hi!']

The curried version of replaceAtIdx wouldn't look too terribly different. It would just look like this:

const replaceAtIdx = (index) => (value) => (array) => {
    return array.map((val, idx) => idx === index ? value : val)
}

replaceAtIdx (2) ('hi') (['zero', 'one', 'two']) // => ['zero', 'one', 'hi!']

Instead of taking all three arguments at once, our curried version of replaceAtIndex breaks the original "trinary"[2] function into three "unary"[3] functions, each returning another function which serves to act as a closure over the previous argument(s) and to take the next argument for the computation. When the last function is applied to its argument, then the block of code runs. That's really all there is to currying. You just break a function of arity N down into N functions of arity 1.

Currying is admittedly a very strange concept when first learning functional programming from a procedural and/or object-oriented background. But it begins to make a lot more sense when you regularly make use of functional composition patterns, which is what we're trying to do!

I'll try to illustrate with example. Let's adapt the code from the section on function composition:

Imagine we have these three curried functions:

const get = (propName) => (obj) => obj[propName]
const truncate = (length) => (str) => str.slice(0, length)
const append = (appendage) => (str) => `${str}${appendage}`

Since they are curried, we can use composeLTR like this:

const extractFormattedDescription = composeLTR(
    get('description'),
    truncate(100),
    append('...')
)

extractFormattedDescription(item) // => 'This is a truncated description...'

Once you start using composition, it becomes very natural to use curried functions. Currying allows you to build specialized functions from more generic functions (like creating getDescription with get('description')). Both of these together form a very powerful little technique that can transform your code base from verbose and full of intermediate variables, to very concise and expressive.

Currying and composition are arguably two of the most powerful programming design patterns for code reuse. You use currying to build specialized functions from more general ones. Simply changing an argument creates a whole new specialized function, yet you didn't have to write any new code; you just invoked a function! You use composition to string together however many functions you need to perform your desired transformation on the value, (leveraging your curried general functions along the way), to build up a super-specialized function that would have taken you twice the code to implement imperatively.

Once you experience the level productivity and flow that comes from solving problems this way, you'll wonder how you ever got anything done before. And once you're hooked on this power, you'll start to get frustrated by the fact that native JS methods and most JS libraries don't use currying. You'll start to wish there were libraries that made it easier to use these basic techniques on every project because of how productive they make you and how clean they make your code base.

Hmm... that sounds like a really good segue for talking about ramda.js. All of their functions are curried and designed specifically for functional composition!


  1. composeLTR is commonly known as pipe ↩︎

  2. trinary - arity three; i.e., takes three arguments ↩︎

  3. unary - arity one; i.e., takes one argument ↩︎