Partial Function Application in JavaScript and Flow

Tradeoffs and implementations

Partial function application is a set of useful techniques which come up frequently in functional programming. This post is a detailed introduction into these techniques, in which we’ll see what partial application is, compare some utilities for performing it, and explore the tradeoffs that partial application presents us with. Finally, we’ll dig into the essence of partial function application: specializing general functions.

Some parts of this post use Flow syntax for describing the types of functions, but the concepts discussed are equally applicable to untyped JavaScript.

Basic definition

function power(exponent: number, base: number) : number {
let result = 1;
for (let i = 0; i < exponent; i++) {
result = result * base;
}
return result;
}

We can use this to raise numbers to some power, like doing power(2, 3) to see that 3 squared is 9, or power(3, 2) to see that 2 cubed is 8. It would be more readable to just say square(3) or cube(2), though. To do this, we want to produce a function that’s the result of “locking-in” 2 and 3 to power. We can do this by manually declaring new functions:

const square = x => power(2, x),
cube = x => power(3, x);

This is partial application: we create square by making a new function, which just passes its arguments through to another function, adding some hard-coded arguments to that other function. We could be more explicit in our intent by making a partialApply function that produces these new functions for us:

const square = partialApply(power, 2);
const cube = partialApply(power, 3);

This partialApply function is quite easy to write, because it turns out that partial application is baked into JavaScript:

function partialApply(fn, ...args) {
return fn.bind(null, ...args);
}

bind is incredibly useful, and shows up frequently in JavaScript code. If you haven’t seen it before, MDN has good documentation on it.

Partial application and types

More advanced partial application

function autoPartial(fn) {  const collect = (boundArgs, ...args) => {  const collectedArgs = boundArgs.concat(args);
return collectedArgs.length >= fn.length ?
fn.apply(null, collectedArgs) :
collect.bind(null, collectedArgs);
};
return collect.bind(null, []);
}

Now we can use a function that takes more arguments, and see that we can partially apply as many or as few arguments as we’d like, and that we can partially apply arguments multiple times:

const deliveryNotification = autoPartial(
(name, job, quantity, item) =>
`Hi, I’m ${name} the ${job}. ` +
`I’ve brought you ${quantity} ${item}`);
const mail = deliveryNotification("Frank",
"postal worker",
5,
"letters");
// Hi, I'm Frank the postal worker. I've brought you 5 letters
const susansDelivery = deliveryNotification("Susan",
"postal worker");
const moreMail = susansDelivery(2, "packages");
// Hi, I'm Susan the postal worker. I've brought you 2 packages
const threeThingsFromSusan = susansDelivery(3);const bills = threeThingsFromSusan("bills");
// Hi, I'm Susan the postal worker. I've brought you 3 bills

This is a handy little utility, which gives us a lot of freedom in how we can use the function that it wraps. But it still has a shortcoming: it can only bind arguments in the order in which they appear in a function’s definition.

If you think back to the beginning of this post, you’ll remember that I gave the power function this signature:

(exponent : number, base: number) => number

Most of us are used to an exponent being written something like , so having the exponent come first in the function’s signature seems odd. But to implement square and cube, the bound argument has to come first. Let’s write a function which lets us skip over some arguments while binding others by explicitly specifying holes in a function’s signature:

Don’t fret the details of this code: what’s important is the interface this gives us

We can call this like so:

const {_, holePartial} = partialFactory();const mailDelivery = holePartial(deliveryNotification, 
_,
"postal worker",
_,
"letters");
const mondaysMail = mailDelivery("Frank", 3),
tuesdaysMail = mailDelivery("Susan", 7);

We need a unique value for our “hole”, because if we used something like null for it, that would get confused with binding null as the actual argument to our function. So holePartial returns an object that it uses as this unique marker internally, which we then use to mark arguments we want to skip. Now we can swap the arguments to our power function such that the base comes first and the exponent comes second, and define square and cube like so:

const square = holePartial(power, _, 2),
cube = holePartial(power, _, 3);

The core of partial application: specialization

All of these are partial application, because partial application is really a family of techniques, not any specific tool or function. Some techniques are more conventional than others, but as long as you’re reducing the number of arguments a function takes by locking in some of the arguments, you’re doing partial application. This reduction of arguments enables the essential benefit of partial function application: it allows us to produce specialized versions of more general functions.

If you consider the first examples in this post, power is a more general function than square or cube, but code calling these functions is more clear than code calling power directly. Using partial application, we are able to solve our problem in a general form, and then specialize this general solution into a more focused solution to a particular task. This sort of relationship exists between the original and partially applied versions of any function: The version with more arguments has more capabilities, because by removing an argument, we remove a caller’s freedom to specify what a function does. However, the version with fewer arguments is easier and clearer to use, because removing an argument also removes the responsibility of figuring out what to pass for that argument from the caller.

In many cases, solving a general problem and then specializing it for our specific cases will give use a better solution than implementing several different specific solutions. This is especially true when we use higher-order functions. For example, many useful functions on arrays may be created by partially applying functions to map, filter, and reduce. Partial function application is an easy way to perform this specialization.

Downsides

A less obvious downside is that partial application‘s specialization of a function serves as a point of indirection between a function’s callers and the main logic of that function, partially decoupling the caller from the implementation. This may sound like a good thing, and it usually is, but in codebases with poor documentation and low code quality, looking at calling code may be the best way to figure out what a function does and how it should be used. In these cases, additional indirection can make this detective work more difficult. It would be better to improve the quality of the code than to swear off partial application, but it’s something to consider when deciding whether these techniques are appropriate for a particular codebase.

The third tradeoff to using more advanced partial application techniques than bind is that, by using a custom solution, you’re breaking convention with the rest of the JavaScript ecosystem. Sure, a custom solution may be more understandable to your team, but every unusual piece of your codebase increases the time and effort needed for a new team member to understand your system as a whole. Whether the costs outweigh the benefits depends on the team, the codebase, and the specific technique.

Finally, creating functions in JavaScript is more expensive than in other languages, so techniques which create lots of functions (like autoPartial) can have a performance penalty. However, even heavy use of most partial application techniques will not be a problem in most cases, so over-concern with this performance cost is likely to lead to premature optimization.

So that’s partial application, from top to bottom. It’s a useful family of techniques which provide interesting tradeoffs to consider. Even in its simplest incarnation as bind, partial application can help us improve our code quality by writing more general functions whose behavior is customized by their arguments, while keeping a focused interface for callers of the function’s specialized form.

In my next post I talk about currying, which is related to partial application but has important differences in both its implementation and its motivation.

All of the source code for the examples in this post is available here.

This post was originally published in a much longer form. Thanks to (((well then…))) and @5ozamericano for reviewing the original.
Thanks to
Lizz Katsnelson for editing this post.

JavaScript developer with a focus on typed functional programming. He/him. https://jnkr.tech