JavaScript Functional Programming Explained: Partial Application and Currying

Peleke Sengstacke
๐Ÿ‘๏ธ 1,595 views
๐Ÿ’ฌ comments

Summary: Currying transforms a function of multiple arguments to a series of function calls, each of which involves just one of those arguments. Partial application fixes the value of some of a function's arguments without fully evaluating the function.


Redux. Reason. Cycle.

Aside from kick-ass names, what these all have in common is functional flavor. Reason is a syntactically C-like dialect of [OCaml]; Redux was inspired by the classically functional Elm Architecture; and Cycle adopts a source/sink architecture that facilitates clean separation of pure and impure elements of an application

Table of Contents

    Given the names and adoption rates behind these ecosystems, the verdict is clear:

    Familiarity with functional programming is non-negotiable for serious programmers.

    Two important ideas with roots in functional thought are currying and partial application. Today, I'd like to 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 how they're different between the two
    • Use partial application to fix arguments to a function
    • Curry functions to facilitate partial application
    • Design functions that facilitate partial application-.

    As prerequisite, I assume you're familiar with closure; higher-order functions (HOFs); and map.

    Let's get to it.

    A Motivating Annoyance

    As with all patterns, partial application is easier to understand with a motivating context.

    Consider this buildUri function.

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

    We call like this:

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

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

    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. Life would be easier, and more elegant, if we didn't repeat ourselves. We'd prefer to 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}`
    }

    ...And it would do precisely what we want.

    But this should feel suspicious. We're essentially duplicating buildUri, but hard-coding https as its scheme argument. These are both code smells.

    Partial application lets us do exactly 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 have a crack at doing it by hand.

    Using Ramda

    Using Ramda, partial application looks like this:

    // Assuming we're in a node enironment
    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')

    ...As desired. Let's break down what happened, here.

    1. We called Ramda's partial function, and passed two arguments: First, a function, called buildUri; and second, an array containing one value: "https"..
    2. 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')

    The most obvious reason this is powerful is that it 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.

    1. On Line 0, we define a function called fixUriScheme. This function accepts a scheme, and returns another function.
    2. 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 one 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')

    This is probably more familiar for most. 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.

    Both approaches are just as good. Which is "better" depends on context: Your team, your project, and your preferences.

    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', '')

    ...See something?

    This timeโ€”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')

    ...I don't know about you, but the first time I saw this I flipped my sh*t.

    Blew my mind.

    A Design Consideration

    Incidentally, this brings up a good point. 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 way better if we could just use partial (or partialRight), instead of both in sequence.

    For us, this is a pretty easy fix. If we redefine buildUrl:

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

    This new version passes the values we're likely to know up-front 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 get away with using 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 & Convenient Partial Application

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

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

    Note that:

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

    There's a slick trick 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.

    Here's the "need to know":

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

    So, currying gives us the best of both worlds: Automatic partial application, and the ability to use our original function "as normal".

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

    There's a lot more to say about currying, in general. In particular, I'd be remiss if I didn't define it.

    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.

    I hid something in the above snippet. 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')

    ...Or, we can call it like this:

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

    ...Yeah, looks weird, I know. 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 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.

    Summary

    Let's recap the major takeaways.

    1. Partial application allows us to fix a function's arguments. This lets us derive new functions, with specific behavior, from other, more general functions. 2.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.
    2. Ramda provides partial, partialRight, and curry utilities. Similar popular libraries include Underscore and Lodash...But I am quite partial to Ramda.

    If you've got questions or use functional programming techniques in your own code, drop a line, or hit me on Twitter @PelekeS.

    Peleke Sengstacke

    16 posts

    Peleke Sengstacke is a web and Android developer with a soft spot for functional programming.

    He likes linguistics, Haskell, and powerlifting. He dislikes mosquitoes, cramped bus rides , and merge conflicts.

    Catch him on Twitter (@PelekeS), or sign up for his email list on what to learn and where to learn it (http://www.tinyletter.com/PelekeS). It's wicked educational.