Back

Forever Functional: Simpler code through Partial Application

Forever Functional: Simpler code through Partial Application

“Partial Application” may sound like a complex thing, but it is actually quite a powerful tool that will simplify the way you work, and this article will show you how to easily implement it for your JavaScript code.

When we discussed higher-order functions such as decorators, we saw several ways of working with a function to produce a new version that would have some change in its functionality, such as adding logging or timing among many examples.

Let’s now consider another kind of transformation that will not change what the function does but will allow you to simplify your work: partial application. If you have a function that expects several parameters, partial application lets you fix the values of some of them, producing a new function that will expect the missing parameters. When you provide all the pending parameters, the original function is called, and the actual calculation is performed.

Why use partial application?

Before getting to the details of how to do partial application, let’s give some examples. Imagine you had an addTax(moneyAmount, taxRate) function that would compute the required taxes for a given money amount along the lines of the following:

const addTax = (moneyAmount, taxRate): number => {
  return moneyAmount + moneyAmount * rate / 100;
}

For example, if you wrote addTax(400, 5), the result would be 420; 400 plus 5% of 400, which is 20. If you always have to add a fixed, constant rate, you can use partial application to produce a specialized version of the addTax(...) function. For instance, in my country (Uruguay) the usual tax rate is 22%, so I could write something like the following:

const addUruguayTax = addTax(undefined, 22);  // this won't (yet) work!

This code doesn´t really work because addTax(...) doesn’t know what to do with the undefined amount, but we’ll get to that; bear with me. As the result of partial application, addUruguayTax(...) would now be a function expecting a single parameter. We could use this function as follows:

const totalSalesPlusUruguayTax = addUruguayTax(totalSales);

Given a totalSales, then totalSalesPlusUruguayTax would include the national VAT; nice!

A possible objection: why go to all this bother when you could just write the following?

const addUruguayTax = (moneyAmount) => addTax(moneyAmount, 22);

The question is valid, and for simple functions such as addTax(...) with just a couple of parameters, using partial application can be overkill. The real benefits come when working with functions that receive many parameters, because now you can be able to fix any of them as you may need.

For instance, if you had a nonSensical(a,b,c,d,e,f) function with six parameters, there are 63 different ways in which you can provide some parameters and leave others unfixed; you wouldn’t be able to handle this easily with plain arrow functions! So, let’s now see how we can write a general partialize(...) solution.

Implementing partial application

In some languages, doing partial application in a totally general way would be hard or impossible, but JavaScript (and therefore TypeScript as well) lets us do it by taking advantage of closures, as we’ll see now.

We first need a way to specify which parameters are to be fixed and which are still pending. Some libraries use special placeholders (like Ramda, which uses R.__, or Underscore, which uses _) but I think undefined fits the bill and can be used to represent missing values.

The following code (from my Mastering JavaScript Functional Programming book) does the job:

function partial(fn) {
  const partialize =
    (...args1) =>
    (...args2) => {
      for (let i = 0; i < args1.length && args2.length; i++) {               1️⃣
        if (args1[i] === undefined) {
          args1[i] = args2.shift();
        }
      }
        
      const allParams = [...args1, ...args2];                                2️⃣
      
      return !allParams.includes(undefined) && allParams.length >= fn.length 
        ? fn(...allParams)                                                   3️⃣
        : partialize(...allParams);                                          4️⃣
    };
  return partialize();
}

This is not simple to understand! The inner partialize() function is key: given a list of parameters (args1) it returns a new function that will expect a second list of parameters ( args2) in the following way:

  • 1️⃣ it will attempt to replace possible undefined values in args1 with values taken from args2.
  • 2️⃣ if any parameters are still left in args2, it will add them to the (updated) list at args1, producing a new allParams list
  • 3️⃣ if allParams doesn’t include any (pending) undefined values and has all the parameters that the fn function expected, the original function is called
  • 4️⃣ otherwise, if some parameters are still pending, the function recursively partializes itself to return a new function with more fixed (but still not all) parameters.

Let’s see a simple example and also go back to our taxes-calculating function.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

Some examples of use

Let’s start with a simple makeBigString(...) function, which expects six values and returns a string with all of them.

const makeBigString = (a, b, c, d, e, f) => `${a}${b}${c}${d}${e}${f}`;

const partialMakeBigString = partial(makeBigString);

/*
 * Or write:
 * 
 * const makeBigString = partial((a,b,c,d,e,f) => `${a}${b}${c}${d}${e}${f}`);
 * 
 */

Then, you can write:

partialMakeBigString(undefined,2,undefined,undefined,5)(undefined,3)(1,4)(6);

// result is "123456"

Let’s follow what is happening here, step by step:

  • first, partialMakeBigString(undefined,2,undefined,undefined,5) returns a new version of the original function that fixes its 2nd and 5th parameters, leaving all the rest undefined.
  • calling that function with (undefined,3) fixes its 2nd parameter (which actually is the 3rd parameter of the original function) and produces a new function that still expects three more parameters
  • calling the latest function with (1,4) fixes the first two of the three parameters and produces a new function that needs just one parameter
  • providing (6) to the last function actually finishes fixing all the parameters of the original function, which is then called to produce "123456".

How would we do our original taxes example? There are two ways. We could use partial application when defining addUruguayTax(...) as in:

const addUruguayTax = partial(addTax)(undefined, 22);

Or (better yet, in my opinion) we could have defined addTax(...) from the beginning to allow partial application:

const addTax = partial((moneyAmount, taxRate): number => {
  return moneyAmount + moneyAmount * rate / 100;
})

Both solutions work, but I think the first one is harder to understand—having two sets of parentheses in a row is not common.

Conclusion

Partial application is an interesting technique that simplifies working with functions, whenever you foresee the possibility or need for providing just a few parameters at a time, producing a function that, at a later time, will receive the pending rest. Implementation takes advantage of closures and recursion, highlighting all the facilities JavaScript includes to allow very functional ways of working!

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay