What Are Partial Applications?

tl;dr;

A Partial Application is a function where some or all of the arguments are packed inside, ready to go and it’s just waiting for a few more before the main function is invoked. They’re like functions that have default arguments, but are pure functions with a fixed amount of parameters.

Introduction

The following article and companion video playlist will cover what a partial application is and how it can be used for a more pure function option for default arguments. It’s assumed you know what pure functions are. We’ll cover:

  • basic function arguments
  • default arguments and how order can make them harder/easier to use
  • function arity
  • function currying with closures and show how the parameter order is reversed compared to default arguments
  • building partial applications to show how to make using default arguments pure
  • creating partial applications with no arguments

Video Playlist

In case you want an interactive example, I’ll walk through each section.

What Are Partial Applications?

Basic Arguments

The function below pings google.com:
const pingGoogle = () => fetch('http://google.com'). If it resolves, Google is there and your computer can talk to the internets. If it throws an error, your computer is probably having wireless trouble.

You call (or invoke) it like pingGoogle(). If you want better logs, you’d go:

pingGoogle()
.then(() => console.log("Connected to the internet!"))
.catch( error => console.log("Not connected to the internet:", error))

In software development, you’re often testing YOUR server, code that will exist in YOUR url, not googles’. Example, I wonder if my blog is up?

const pingJessesBlog = () => fetch('http://jessewarden.com')

However, while small, those are all hardcoded. While not a religion, it DOES violate DRY: Don’t Repeat Yourself. How can we make the function more re-usable? Parameters or arguments!

const ping = url => fetch(url)

Rad, now we can test Google again:

ping('http://google.com')

Or my blog:

ping('http://jessewarden.com')

… or even see if we’re connected to the internet first, THEN my blog:

ping('http://google.com')
.then(() => ping('http://jessewarden.com'))
.catch(error => console.log("Something failed:", error))

Default Arguments & Pure Functions

However, we know if we call ping, we’re going to do this google thing a lot, so to prevent ourselves from typing so much, let’s just default to “http://google.com” unless someone gives us a URL:

const ping = (url='http://google.com') => fetch(url)

Now, we only have to supply the URL parameter if we want something other than Google:

ping()
.then(() => ping('http://jessewarden.com'))
.catch(error => console.log("Something failed:", error))

Notice that the function ping is now pre-baked with an argument as a backup. “If you don’t send me one, or send me undefined, I’ll just use ‘http://google.com’ instead.” This is what’s called a default argument. If you supply something, great, but if you don’t, still great, we got your back.

Now, ping is not a pure function. A pure function is:

  1. same input, same output
  2. no side effects

A more pure version would be if we forced the developer to declare where they are getting the dependency fetch:

const pingPure = (fetch, url='http://jessewarden.com') => fetch(url))

All we changed was requiring fetch as the first parameter, the 2nd, url is still optional and defaults to Google. Now the fetch has side effects, like making an HTTP call, but as long as we handle the .catch or use a try/catch with async/await syntax, it’s good enough.

ping(fetch)
.then(() => ping(fetch, 'http://jessewarden.com'))
.catch(error => console.log("Something failed:", error))

Function Parameter Order

… however, that’s a pain in the ass. As you can see argument order can really make a function hard to use, and merely thinking about argument order, or testing a function out, then iterating on a new version of it with a different order can make things easier. Let’s do that by reversing the order, AND making fetch have a default as well.

const pingPure = (url='http://jessewarden.com', fetchModule=fetch) => fetchModule(url))

Now, it’s easy to use again, but can be more pure:

ping()
.then(() => ping('http://jessewarden.com'))
.catch(error => console.log("Something failed:", error))

Nice, and if you’re in older browsers, or even in Node but want fetch, you can go:

import fetch from 'cross-fetch' // polyfill for older browsers, or if you're in Node

ping(undefined, fetch)
.then(() => ping('http://jessewarden.com', fetch))
.catch(error => console.log("Something failed:", error))

Note we pass undefined manually. In JavaScript function parameters, undefined is the same thing as “not passing anything”. The function says, “Well, I didn’t get anything, I guess I’ll use the default then.” which is google.

Pure, flexible, easy to use.

What we learned:

  • default arguments make functions easier to use by requiring less arguments, and providing reasonable defaults
  • the developer using them can type less
  • less typing, but more importantly, less reading makes the code easier to understand
  • pure functions require same input, same input, and no side effects
  • pure functions more importantly, though, have to get all variables they don’t make themselves from function arguments, in this case fetch.
  • function parameter order really helps a function be more useable and easier to read

Function Arity

Let’s briefly cover function arity. You can learn more here https://jesterxl.github.io/real-world-functional-programming-book/part4/arity.html or watch a short video here https://www.youtube.com/watch?v=NoITQ4jU6jg

Function Arity is how many parameters/arguments does a function have. yo() has 0, dude(wat) has 1, and so on. You can query this via theFunction.length.

console.log(yo.length) // 0
console.log(dude.length) // 1

However, deafult arguments don’t count against that. Worse, if you put them first, it’ll basically say “Oh, if the first argument has a default, I guess the entire function doesn’t require any arguments, and thus has an Arity of 0”.

So our pingPure above would have an arity of 0.

What we learned:

  • Function Arity means how many parameters does a function take
  • … unless you use default arguments, then arity is meaningless in JavaScript, heh

Currying

Now a fundamental question: Do default arguments affect function purity?

The answer: Yes.

While things like Strings are copied by val, like our ‘http://google.com’, the Objects/Arrays are not.

const pingPure = (url='http://jessewarden.com', fetchModule=fetch) => fetchModule(url))

Note the fetch; where does that come from? A global or closure defined variable and it only knows that when the function is run (or defined if you use function declarations, like function pingPure vs fat arrow functions).

So how do we make it pure, yet still allow default arguments?

Currying, also known as function currying. Currying ensures all functions take 1, and only 1, argument. To say it another way, all functions return functions until the last argument is passed; THAT is the one that actually triggers all the work.

Now functions that return functions isn’t some “new, whack” thing. For example, you can wrap things in closures that return functions:

const ping = url => fetch(url)
const pingGoogle = () => {
    return ping('http://google.com')
}

Now you have a function that returns a function… which returns a Prmoise. Notice how we baked the google URL into the function itself. It’s in the closure, and stored there, ready for use. That’s how currying can work in JavaScript.

You can also use it for debugging, like in the Node debug module: https://www.npmjs.com/package/debug

var debug = require('debug')('ping')

const ping = () => {
    debug("Pinging Google...")
    return fetch('http://google.com')
    .then(result => {
        debug("Done!")
    })
    .catch(error => {
        debug("Oh, it went boom:", error)
    })
}

Before module systems like Node, Require.js or ES6/Webpack formalized things, people would define modules using Immediately Invoked Function Expressions, or IIFE.

(function () {
    function Person(name) {
        this.name = name
    }
    Person.prototype.sayName = function() { 
        console.log("this.name:", this.name)
    }
    return Person
})()

Notice the last () actually invokes the function, but since the variables are local, you don’t have to worry about your Person class accidentally destroying another, previously defined class on window or global in Node. JQuery used to have version conflicts all the time when everyone started using it, and people would include it, and you never had an idea of which version you were using, when because of that, so IIFE fixed it.

However, none of those functions are pure; they don’t declare their dependencies in the function arguments; they’re magical closures (i.e. the debug module, or the fetch module).)

The easiest way is to use a library like Ramda or Lodash to take your existing code and curry it, but I and others now do it the manual way:

const ping = url => fetch => fetch(url)

What this means, is instead of calling it like before:
ping('http://google.com', fetch)

You have to call it a slightly weirder way:

ping('http://google.com')(fetch)

This is why Dr Axel Rauschmayer says currying is not idiomatic JavaScript. What is idiomatic JavaScript, heh?

The reason is the first parameter returns a function, the 2nd invokes the returned function. To say it another way:

const allINeedIsFetch = ping('http://google.com')
console.log(allINeedIsFetch) // function
const result = allINeedIsFetch(fetch) // Promise

To do it the old fashioned way:

function ping(url) { 
    return function(fetch) { 
        return fetch(url)
    } 
}

Yeah no, heh! Using Lodash/Ramda, you could just:

const curry = require('lodash/fp/curry')

const ping = (url, fetch) => fetch(url)
const pingCurried = curry(ping)

Now, you can do either way, and both work:

pingCurried('http://google.com', fetch)
pingCurried('http://google.com')(fetch)

What We Learned:

  • function currying is ensuring all functions only take 1 argument
  • functions will return functions until the last argument is passed, then it actually runs
  • libraries like Lodash/Ramda allow you to take existing, normal comma functions and make the curried, but they still work both ways

I don’t have time to cover how this works with functions that already have default arguments, heh.

Partial Application

… so what’s a partial application?

A partial application is what a curried function returns when you didn’t call the last argument.

If we take our curried ping function:

const ping = url => fetch => fetch(url)

… and call it with both arguments:

const result = ping('http://google.com')(fetch)
console.log(result) // Promise

… it runs our function and returns a Promise.

But what happens if we call it with just 1 parameter, the url?

const wat = ping('http://google.com')
console.log(wat) // function

The wat is a function. If you log it out int he browser, it actually looks like this:

fetch => fetch(url)

Rad, right? But wait… where is this url coming from? The closure! Same as if we did like before:

const url = 'http://google.com'
const wat = fetch => fetch(url)

However, unlike the above, we don’t need a variable; the function “encloses” it, and keeps it safe, keeps it pure.

So, one could say you have a function that’s “partially applied”. The word apply comes from Category Theory where you apply functions to arguments. Basically it means, “the function takes 2 arguments, you gave us 1 of them, so take this function, and when you have the 2nd parameter ready, give it to that function I gave you, it’ll actuall do the work.”.

And THAT is how you do pure default arguments.

const pingGoogle = ping('http://google.com')

Now, you just have to supply fetch, and she’s good to go!

pingGoogle(fetch)

Static Left, Dynamic Right

However, we always know we’re going to use fetch, that’s kind of dumb; the only dynamic part is really the URL. As you saw above using default arguments, the dynamic stuff like URL’s typically goes to the left. The stuff you know that’s static, not changing much like fetch which is a module most poeple in the browser use, is to the right.

It’s reversed in currying. Static left, dynamic right, like so:

const ping = fetch => url => fetch(url)

Once you do that, you can now do that thing we did above:

const pingPartial = ping(fetch)

pingPartial('http://google.com')
.then(() => pingPartial('http://jessewarden.com'))
.then(() => console.log("Pinged blog!"))
.catch(error => console.log("Failed:", error))

Partial Applications With No Arguments

… but, how do you make a partial application with no arguments? It’s hard, but for now, I’ll show you the Lodash way; they have a function called partial that makes it easy:

const partial = require('lodash/fp/partial')
const ping = (fetch, url) => fetch(url)

const pingPartial = partial(ping, [fetch])
const pingGoogle = partial(ping, [fetch, 'http://google.com'])
const pingJesse = partial(ping, [fetch, 'http://jessewarden.com'])

To use:

pingGoogle()
.then(() => pingJesse())
.then(() => console.log("Pinged blog!"))
.catch(error => console.log("Failed:", error))

What we learned:

  • function partials are what curried functions return if you don’t give ’em all their arguments
  • they allow you to use default arguments in a pure way
  • if you want a partial application with no arguments, use a library like Lodash/Ramda

Conclusion

A partial application is a pure function with some or all of its arguments stored inside as a closure. If you are using default arguments a lot, it is a more pure way to accomplish that. Partial applications are created by giving only some of the required arguments to a curried function; the return value will be a partial application until all of the arguments have been given. Parameter order for default arguments tends to favor known, static parameters to the right, and unknown, dynamic data to the left. Curried functions are the opposite. These parameter orders make the function types easier to use.