DEV Community

YCM Jason
YCM Jason

Posted on • Edited on

How to make functions partially applicable in JavaScript

Update: Learn how to type PartiallyApplicable in TypeScript

TL; DR

const enablePartialApplication = (fn) => (...args) => {
    if (args.length >= fn.length) return fn(...args);
    return enablePartialApplication(fn.bind(null, ...args));
};
Enter fullscreen mode Exit fullscreen mode

What is partial function application?

Partial function application describes the ability to partially apply some arguments to a function. Sounds abstract? Let's look at some example.

Let's say we have a function add which simply adds two numbers:

const add = (x, y) => x + y;
add(3, 5); // 8
Enter fullscreen mode Exit fullscreen mode

If we only supply one argument, the result yields to NaN.

add(3) // basically equivalent to: add(3, undefined)
// -> 3 + undefined
// -> NaN
Enter fullscreen mode Exit fullscreen mode

Pretty straightforward?

Some functional programming languages would handles this differently however. For example, if Haskell were to handle add(3), instead of executing the function body x + y, it will do something like the following:

// let's pretend haskell is running the following JavaScript
const addThreeTo = add(3);
// addThreeTo is basically the function: (y) => 2 + y
addThreeTo(5); // 8
// or simply
add(3)(5); // 8

// but we could still do
add(3, 5); // 8
Enter fullscreen mode Exit fullscreen mode

Notice how we supply the second argument 5 by doing addThreeTo(5).

Haskell detects the number of arguments supplied. When it is less than what is expected in the function definition, instead of executing the function body, it returns another function that accepts the remaining arguments. This is called partial function application.

Okay. That's quite cool. But why do we need this? Well you don't. But it is something very convenient to have.

Imagine having a list of numbers and we wish to add 5 to each of them, without partial function application, we have to wrap an arrow function around add.

const add = (x, y) => x + y;
[1, 2, 3, 4, 5, 6].map(i => add(5, i));
Enter fullscreen mode Exit fullscreen mode

However, with partial function application, we could do something like:

// let's pretend haskell is running the following JavaScript
const add = (x, y) => x + y;
[1, 2, 3, 4, 5, 6].map(add(5));
Enter fullscreen mode Exit fullscreen mode

Arguments fixing

Partial function application can also be thought of as fixing arguments.

// let's pretend haskell is running the following JavaScript
const fn = (x, y, z) => (x * y) / z;
const fnFixedX = fn(3); // fixes x to 3: (y, z) => (3 * y) / z
fnFixedX(2, 1); // (3 * 2) / 1 -> 6
Enter fullscreen mode Exit fullscreen mode

Or we could also fix x and y altogether.

// let's pretend haskell is running the following JavaScript
const fn = (x, y, z) => (x * y) / z;
const fnFixedXY = fn(3, 2); // fixes x to 3: (z) => (3 * 2) / z
fnFixedXY(1); // (3 * 2) / 1 -> 6
Enter fullscreen mode Exit fullscreen mode

I hope you have understood what partial function application is by now. If not, read again before continuing.

Implementation

In the previous examples, we pretend that haskell is running the JavaScript in order to illustrate what partial function application is. Now can we actually implement something to enable partial function application in JavaScript? Yes we could!

Let's define a function enablePartialApplication(fn) which would return a function that we could use partial function application.

What we want to achieve:

const sum = enablePartialApplication((x, y) => x + y);
const sum3 = sum(3);
sum3(10); // 13
sum(3, 5); // 8
sum(3)(4); // 7
Enter fullscreen mode Exit fullscreen mode

Another example:

const fn = enablePartialApplication((x, y, z) => (x * y) / z);
fn(3, 2, 1); // (3 * 2) / 1 -> 6
fn(3, 2)(1); // 6
fn(3)(2, 1); // 6
fn(3)(2)(1); // 6
Enter fullscreen mode Exit fullscreen mode

Skeleton

The function input and output are obvious, so we could sketch the skeleton of our function:

const enablePartialApplication = (fn) => {
    return () => {

    };
};
Enter fullscreen mode Exit fullscreen mode

Looking at the arguments

As I mention earlier, Haskell look at 1) the number of arguments passed in and 2) the number of arguments expected by the definition of the function to decide whether partial application is needed.

The first one is simple, we can just use the rest operator and take in the arguments as a list, then do .length() on it.

const enablePartialApplication = (fn) => {
    return (...args) => { // use rest operator to take arguments as a list
        args.length // number of arguments passed in
    };
};
Enter fullscreen mode Exit fullscreen mode

The second one is also not that hard, we could use Function.length. See here for the documentation.

const enablePartialApplication = (fn) => {
    return (...args) => {
        args.length // number of arguments passed in
        fn.length // number of arguments expected by fn
    };
};
Enter fullscreen mode Exit fullscreen mode

We know that it is a partial function application if args.length < fn.length, otherwise, i.e. args.length >= fn.length, it would just be a simple function invocation. So let's put this into our function:

const enablePartialApplication = (fn) => {
    return (...args) => {
        if (args.length >= fn.length) return fn(...args); // function invocation
        // partial function application here
    };
};
Enter fullscreen mode Exit fullscreen mode

Fixing arguments with Function.prototype.bind

Recall the argument fixing analogy, does it remind you of some functions in JavaScript? Yes! Function.prototype.bind! We could fix arguments of a function with it!

const add = (x, y) => x + y;
const add3 = add.bind(null, 3); // the first argument is the thisArg
add3(5); // 8
add3.length; // 1
Enter fullscreen mode Exit fullscreen mode

We could simply bind the input args to fn so that the bound function would expect the remaining arguments.

const enablePartialApplication = (fn) => {
    return (...args) => {
        if (args.length >= fn.length) return fn(...args);
        return fn.bind(null, ...args); // use Function.prototype.bind to fix arguments
    };
};
Enter fullscreen mode Exit fullscreen mode

This implementation would allow us to do the following:

const sum = enablePartialApplication((x, y) => x + y);
const sum3 = sum(3);
sum3(10); // 13
sum(3, 5); // 8
sum(3)(4); // 7

const fn = enablePartialApplication((x, y, z) => (x * y) / z);
fn(3, 2, 1); // (3 * 2) / 1 -> 6
fn(3, 2)(1); // 6
fn(3)(2, 1); // 6
Enter fullscreen mode Exit fullscreen mode

Awesome! However, the returned bound function do not support partial application. So the following would not work

const fn = enablePartialApplication((x, y, z) => (x * y) / z);
fn(3)(2)(1); // Trying to partially apply the function: fn(3)
// -> ((y, z) => (3 * y) / z)(2)(1)
// -> ((3 * 2) / undefined)(1)
// -> (NaN)(1)
// -> ERROR: NaN is not a function
Enter fullscreen mode Exit fullscreen mode

Recursion

Now all we have to do, is make the bound function partially applicable. Easy! Recursivly call enablePartialApplication on it!

const enablePartialApplication = (fn) => {
    return (...args) => {
        if (args.length >= fn.length) return fn(...args);
        return enablePartialApplication(fn.bind(null, ...args)); // make the bound function partially applicable
    };
};
Enter fullscreen mode Exit fullscreen mode

With this implementation, we could chain as much as we want!

const g = enablePartialApplication((a, b, c, d, e, f) => a + b - c * d / e ** f);
g(1, 2, 3, 4, 5, 6); // 2.999232
g(1, 2)(3, 4, 5)(6); // 2.999232
g(1)(2)(3)(4)(5)(6); // 2.999232
// ...
Enter fullscreen mode Exit fullscreen mode

AWESOME!

Clean up

Notice our function enablePartialApplication returns another function immediately. We can therefore simplify this with:

const enablePartialApplication = (fn) => (...args) => {
    if (args.length >= fn.length) return fn(...args);
    return enablePartialApplication(fn.bind(null, ...args));
};
Enter fullscreen mode Exit fullscreen mode

Like this? Share and comment! :D

Top comments (7)

Collapse
 
gmartigny profile image
Guillaume Martigny

If you don't care for readability, you can write it as a tiny on-liner :

const enablePartialApplication = fn => (...args) => args.length >= fn.length ? fn(...args) : enablePartialApplication(fn.bind(null, ...args));

It nice to have such power in one line.

Collapse
 
tarik12009 profile image
tarik12009 • Edited

I believe that this concept is called "Currying" : en.wikipedia.org/wiki/Currying

Collapse
 
srishanbhattarai profile image
Srishan Bhattarai

FP 101 - Currying vs. Partial application

Currying is converting one function of arity n into n functions of arity one.
There is no requirement that partial application be bound to a particular arity (one, in the case of currying), it simply states that if you call a function with only some of the arguments - you get back another function "binds" or "fixes" the arguments that you passed in, as demonstrated by the author here. It's a very prevalent concept in FP languages like Haskell.

Collapse
 
ycmjason profile image
YCM Jason • Edited

Thanks for clarifying this! I did do my research before deciding what keyword to use. I also thought curry is the same as partial function application. It's great someone could verify my word choice! :D

Collapse
 
srishanbhattarai profile image
Srishan Bhattarai

If nothing else, it forces you to think in ways you wouldn't otherwise. I believe Larry Wall (creator of Perl) recommends Haskell as an "academic" language for this reason :)

Collapse
 
inf3rno profile image
inf3rno • Edited

It can be useful if you design a library and you are thinking on the proper api. It can be combined for example with fluent interface: x(a).y(b)(c) etc. So it is good only for sugar syntax.

Collapse
 
bernardoow profile image
Bernardo Gomes

I used partial function with elm-lang.
Thanks for sharing !!