JavaScript Functional Programming Explained: Partial Application and Currying

PostedDecember 12, 2019 1.6k views DevelopmentJavaScript

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Introduction

With the adoption of the Redux JavaScript library, the Reason syntax extension and toolchain, and the Cycle JavaScript framework, functional programming with JavaScript is becoming increasingly relevant. Two important ideas with roots in functional thought are currying, which transforms a function of multiple arguments into a series of function calls, and partial application, which fixes the value of some of a function’s arguments without fully evaluating the function. In this article, we will explore some examples of these ideas in action, as well as identify a few places they show up that might surprise you.

After reading this, you’ll be able to:

  • Define partial application and currying and explain the difference between the two.
  • Use partial application to fix arguments to a function.
  • Curry functions to facilitate partial application.
  • Design functions that facilitate partial application.

Example without Partial Application

As with many patterns, partial application is easier to understand with context.

Consider this buildUri function:

function buildUri (scheme, domain, path) {
  return `${scheme}://${domain}/${path}`
}

We call like this:

buildUri('https', 'twitter.com', 'favicon.ico')

This produces the string https://twitter.com/favicon.ico.

This is a handy function if you’re building lots of URLs. If you work mainly on the web, though, you’ll rarely use a scheme other than http or https:

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
const googleHome = buildUri('https', 'google.com', '')

Note the commonality between these two lines: Both pass https as an initial argument. We’d prefer to cut out the repetition and write something more like:

const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

There are a couple of ways to do this. Let’s see how to achieve it with partial application.

Partial Application: Fixing Arguments

We agreed that, instead of:

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')

We’d prefer to write:

const twitterFavicon =  buildHttpsUri('twitter.com', 'favicon.ico')

Conceptually, buildHttpsUri does exactly the same thing as buildUri, but with a fixed value for its scheme argument.

We could implement buildHttpsUri directly like so:

function buildHttpsUri (domain, path) {
  return `https://${domain}/${path}`
}

This will do what we we want, but has not yet solved our problem completely. We’re duplicating buildUri, but hard-coding https as its scheme argument.

Partial application lets us do this, but by taking advantage of the code we already have in buildUri . First, we’ll see how to do this using a functional utility library called Ramda. Then, we’ll try doing it by hand.

Using Ramda

Using Ramda, partial application looks like this:

// Assuming we're in a node environment
const R = require('ramda')

// R.partial returns a new function (!)
const buildHttpsUri = R.partial(buildUri, ['https'])

After that, we can do:

const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

Let’s break down what happened here:

  • We called Ramda’s partial function, and passed two arguments: First, a function, called buildUri, and second, an array containing one "https" value.
  • Ramda then returns a new function, which behaves like buildUri, but with "https" as its first argument.

Passing more values in the array fixes further arguments:

// Bind `https` as first arg to `buildUri`, and `twitter.com` as second
const twitterPath = R.partial(buildUri, ['https', 'twitter.com'])

// Outputs: `https://twitter.com/favicon.ico`
const twitterFavicon = twitterPath('favicon.ico')

This allows us to reuse general code we’ve written elsewhere by configuring it for special cases.

Manual Partial Application

In practice, you’ll use utilities like partial whenever you need to use partial application. But, for the sake of illustration, let’s try to do this ourselves.

Let’s see the snippet first, and then dissect.

// Line 0
function fixUriScheme (scheme) {
  console.log(scheme)
  return function buildUriWithProvidedScheme (domain, path) {
    return buildUri(scheme, domain, path)
  }
}

// Line 1
const buildHttpsUri = fixUriScheme('https')

// Outputs: `https://twitter.com/favicon.ico`
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

Let’s break down what happened.

  • On Line 0, we define a function called fixUriScheme. This function accepts a scheme, and returns another function.
  • On Line 1, we save the result of calling fixUriScheme('https') into a variable called buildHttpsUri, which behaves exactly the same as the version we built with Ramda.

Our function fixUriScheme accepts a value, and returns a function. Recall that this makes it a higher-order function, or HOF. This returned function only accepts two arguments: domain and path.

Note that, when we call this returned function, we only explicitly pass domain and path, but it remembers the scheme we passed on Line 1. This is because the inner function, buildUriWithProvidedScheme, has access to all of the values in its parent function’s scope, even after the parent function has returned. This is what we call closure.

This generalizes. Any time a function returns another function, the returned function has access to any variables initialized within the parent function’s scope. This is a good example of using closure to encapsulate state.

We could do something similar using an object with methods:

class UriBuilder {

  constructor (scheme) {
    this.scheme = scheme
  }

  buildUri (domain, path) {
    return `${this.scheme}://${domain}/${path}`
  }
}

const httpsUriBuilder = new UriBuilder('https')

const twitterFavicon = httpsUriBuilder.buildUri('twitter.com', 'favicon.ico')

In this example, we configure each instance of the UriBuilder class with a specific scheme. Then, we can call the buildUri method, which combines the user’s desired domain and path with our pre-configured scheme to produce the desired URL.

Generalizing

Recall the example we started with:

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')

const googleHome = buildUri('https', 'google.com', '')

Let’s make a slight change:

const twitterHome = buildUri('https', 'twitter.com', '')

const googleHome = buildUri('https', 'google.com', '')

This time there are two commonalities: The scheme, "https", in both cases, and the path, here the empty string.

The partial function we saw earlier partially applies from the left. Ramda also provides partialRight, which allows us to partially apply from right to left.

const buildHomeUrl = R.partialRight(buildUri, [''])

const twitterHome = buildHomeUrl('https', 'twitter.com')
const googleHome = buildHomeUrl('https', 'google.com')

We can take this further:

const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https'])

const twitterHome = buildHttpsHomeUrl('twitter.com')
const googleHome = buildHttpsHomeUrl('google.com')

A Design Consideration

To fix both the scheme and path arguments to buildUrl, we had to first use partialRight, and then use partial on the result.

This isn’t ideal. It would be better if we could use partial (or partialRight), instead of both in sequence.

Let’s see if we can fix this. If we redefine buildUrl:

function buildUrl (scheme, path, domain) {
  return `${scheme}://${domain}/${path}`
}

This new version passes the values we are likely to know upfront first. The last argument, domain, is the one we’re most likely to want to vary. Arranging arguments in this order is a good rule of thumb.

We can also use only partial:

const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])

This drives home the point that argument order matters. Some orders are more convenient for partial application than others. Take time to think about argument order if you plan to use your functions with partial application.

Currying and Convenient Partial Application

We’ve now redefined buildUrl with a different argument order:


function buildUrl (scheme, path, domain) {

  return `${scheme}://${domain}/${path}`

}

Note that:

  • The arguments we are most likely to want to fix appear on the left. The one we want to vary is all the way on the right.
  • buildUri is a function of three arguments. In other words, we need to pass three things to get it to run.

There’s a strategy we can use to take advantage of this:

const curriedBuildUrl = R.curry(buildUrl)

// We can fix the first argument...
const buildHttpsUrl = curriedBuildUrl('https')
const twitterFavicon = buildHttpsUrl('twitter.com', 'favicon.ico')

// ...Or fix both the first and second arguments...
const buildHomeHttpsUrl = curriedBuildUrl('https', '')
const twitterHome = buildHomeHttpsUrl('twitter.com')

// ...Or, pass everything all at once, if we have it
const httpTwitterFavicon = curriedBuildUrl('http', 'favicon.ico', 'twitter.com')

The curry function takes a function, curries it, and returns a new function, not unlike partial.

Currying is the process of transforming a function that we call all at once with multiple variables, like buildUrl, into a series of function calls, where we pass each variable one at a time.

  • curry doesn’t fix arguments immediately. The returned function takes as many arguments as the original function.
  • If you pass all the necessary arguments to the curried function, it will behave like buildUri.
  • If you pass fewer arguments than the original function took, the curried function will automatically return the same thing you’d get by calling partial.

Currying gives us the best of both worlds: Automatic partial application and the ability to use our original functions.

Note that currying makes it easier to create partially applied versions of our functions. This is because curried functions are convenient to partially apply, as long as we’ve been careful about our argument ordering.

We can call curriedbuildUrl the same way we’d call buildUri:

const curriedBuildUrl = R.curry(buildUrl)

// Outputs: `https://twitter.com/favicon.ico`
curriedBuildUrl('https', 'favicon.ico', 'twitter.com')

We can also call it like this:

curriedBuildUrl('https')('favicon.ico')('twitter.com')

Note that that curriedBuildUrl('https') returns a function, which behaves like buildUrl, but with its scheme fixed to "https" .

Then, we immediately call this function with "favicon.ico". This returns another function, which behaves like buildUrl, but with its scheme fixed to"https" and its path fixed to the empty string.

Finally, we invoke this function with "twitter.com". Since this is the last argument, the function resolves to the final value of: http://twitter.com/favicon.ico.

The important takeaway is: curriedBuldUrl can be called as a sequence of function calls, where we pass only one argument with each call. It is the process of converting a function of many variables passed “all at once” into such a sequence of “one-argument calls” that we call currying.

Conclusion

Let’s recap the major takeaways:

  • Partial application allows us to fix a function’s arguments. This lets us derive new functions, with specific behavior, from other, more general functions.
  • Currying transforms a function that accepts multiple arguments “all at once” into a series of function calls, each of which involves only one argument at a time. Curried functions with a well-designed argument order are convenient to partially apply.
  • Ramda provides partial, partialRight, and curry utilities. Similar popular libraries include Underscore and Lodash.

0 Comments

Creative Commons License