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:
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.
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, 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:
partial
function, and passed two arguments: First, a function, called buildUri
, and second, an array containing one "https"
value.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.
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.
fixUriScheme
. This function accepts a scheme
, and returns another function.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.
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')
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.
We’ve now redefined buildUrl
with a different argument order:
function buildUrl (scheme, path, domain) {
return `${scheme}://${domain}/${path}`
}
Note that:
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.buildUri
.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.
Let’s recap the major takeaways:
partial
, partialRight
, and curry
utilities. Similar popular libraries include Underscore and Lodash.Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Hi, I had a question!
When you manually built buildHttpsUri() w/partial application, I see that within buildUriWithProvidedScheme() you called buildUri(). I understand the whole process of it, but was wondering if there was any technical term(s) for that situation(creating a function => returning a new function => invoking a function within that function)?
Also, would buildUriWithProvidedScheme() be a HOF as well, because it returned buildUri()? Or not, because buildUri() declared prior?
Thanks for the article, by the way! Great explanation and examples!! Appreciate your work!