DEV Community

Cover image for Curried functions
Tracy Gilmore
Tracy Gilmore

Posted on • Edited on

Curried functions

If you are not interested in the historic context then please skip to the "Let's get started" section.

Dr Haskell Brooks Curry was a mathematician and logician of the 1930's. Through his work in combinatorics and lambda calculus his name has been immortalised in the programming languages Curry, Brook and Haskell. His surname is also associated with a computer science technique for transforming functions.

More in keeping with my series on "A little computer science for the inquisitive developer" we will take a moment to learn just enough about lambda calculus.

Introduced in the 1930's by mathematician Alonzo Church, lambda calculus is a formalised (mathematical) method for defining functions as lambda expressions.

Function parameters are defined up front in lambda expressions, denoted by the prefixed Greek letter lambda λ and separated by a dot character. The other side of the last dot character comes the formula of the function. For example the JS function to implement x squared could be.

function squared(x) {
  return x * x;
}
Enter fullscreen mode Exit fullscreen mode

The lambda expression equivalent would be (λx.x * x), which might be reminiscent of the JS arrow function syntax,

const squared = x => x * x;  // or x**2;
Enter fullscreen mode Exit fullscreen mode

One key difference between lambda expressions and JS functions is the way the inputs operate. In regular functions (and arrow functions) all the arguments have to be supplied (bound to parameters) when called and then the function will be performed. With lambda expressions there is no expectation all the inputs will be bound at once or even in the stated order.

Currying goes one step further, requiring one parameter to be supplied at a time as follows.

λx.(λy.x * y)

This is equivalent to the arrow function:

const curriedProduct = x => y => x * y;

const times6 = curriedProduct(6);

console.log(times6(7)); // 42
Enter fullscreen mode Exit fullscreen mode

Let's get started

In a previous post in this series on "Going functional one step at a time" we investigated the FP concept of lenses using partial-application implemented in a variety of ways.

NB: Some FP purists will probably disagree with some if not all of this post, apologies. This post describes my understanding of the topic (as a JS developer) in a way I find useful, and hopefully so will you.

These is another FP concept called "Currying", which occasionally appears to get confused with partial-application. Whilst related, in my mind they are distinct concepts.

Partial-application is a mechanism that enables the code to call a function several times providing more arguments with each call. Once all the parameters of the function have been supplied (aka bound to an argument) the function will execute.

Currying is a process (at least in JavaScript) that converts a function that expects multiple arguments all at once and executed immediately, in to a function that expects arguments to be supplied/bound one at a time. Although some implementations, including the one below, can bind multiple arguments to parameters on each call. The function will only execute once all the required parameters have been bound to arguments, until then a new function is returned.

Four-stage partial-application

As an example we will use the scenario of filtering an array of objects to extract those objects that match a search term in some way. The executing function will be a predicate that takes in an object (from an array) and returns a Boolean value. This enables us to use the filter method of the array to select compliant objects.

The filterBySearchTerm function will require four arguments, supplied one at a time.

  1. First we will provide a function used to compare the search term with the object property.
  2. Next we identify the name of the property to be matched.
  3. Then supply the search term just before
  4. we finally pass each item from the array to the function within a filter operation.

Test data

Here is the array of data we will be using to demonstrate the working function.

const testData = [
  {name: 'Alice', age: 31},
  {name: 'Bob', age: 32},
  {name: 'Charlie', age: 33},
  {name: 'David', age: 34},
  {name: 'Eve', age: 35},
  {name: 'Fred', age: 36}
];
console.table(testData);

/*
┌─────────┬───────────┬─────┐
│ (index) │   name    │ age │
├─────────┼───────────┼─────┤
│    0    │  'Alice'  │ 31  │
│    1    │   'Bob'   │ 32  │
│    2    │ 'Charlie' │ 33  │
│    3    │  'David'  │ 34  │
│    4    │   'Eve'   │ 35  │
│    5    │  'Fred'   │ 36  │
└─────────┴───────────┴─────┘
*/
Enter fullscreen mode Exit fullscreen mode

Execution and expected results

Let's skip to see how the story ends, happily.

const nameContains = filterContains('name'); // prop
const nameContainsTheSearchTerm = nameContains('e');

const results = testData.filter(nameContainsTheSearchTerm);
console.table(results);

/*
┌─────────┬───────────┬─────┐
│ (index) │   name    │ age │
├─────────┼───────────┼─────┤
│    0    │  'Alice'  │ 31  │
│    1    │ 'Charlie' │ 33  │
│    2    │   'Eve'   │ 35  │
│    3    │  'Fred'   │ 36  │
└─────────┴───────────┴─────┘
*/
Enter fullscreen mode Exit fullscreen mode

Notice the search term is a string containing a single character and the predicate generating function is called nameContains in this example.

We will use the same curried function filterConstuctor to perform the following example where the search term searchAge is a numeric value and the predicate generator is called filterGreaterThanAge32.

const searchAge = 32;
const filterGreaterThanAge = filterGreaterThan('age');
const filterGreaterThanAge32 = filterGreaterThanAge(searchAge);

const results = testData.filter(filterGreaterThanAge32);
console.table(results);

/*
┌─────────┬───────────┬─────┐
│ (index) │   name    │ age │
├─────────┼───────────┼─────┤
│    0    │ 'Charlie' │ 33  │
│    1    │  'David'  │ 34  │
│    2    │   'Eve'   │ 35  │
│    3    │  'Fred'   │ 36  │
└─────────┴───────────┴─────┘
*/
Enter fullscreen mode Exit fullscreen mode

So how do we use and how can we write the filterConstuctor function to generate the nameContainsTheSearchTerm and filterGreaterThanAge32 predicate generators?

Using the filterConstuctor

The predicates generators are constructed by first supplying the comparison functions as follows.

const filterContains = filterConstuctor(
  (prop, searchTerm) => prop.includes(searchTerm)
);

// and

const filterGreaterThan = filterConstuctor(
  (prop, searchAge) => prop > searchAge
);
Enter fullscreen mode Exit fullscreen mode

These functions are called to supply the name of the property within the objects to be compared:

const nameContains = filterContains('name'); // prop

// and

const filterGreaterThanAge = filterGreaterThan('age'); // prop
Enter fullscreen mode Exit fullscreen mode

We can use these functions indirectly (in point-free style) or directly. Both work equally as well and with well-chosen (do as I say not as I do) names the intention can be obvious either way.

// Indirectly
const nameContainsTheSearchTerm = nameContains('e');

const results = testData.filter(nameContainsTheSearchTerm);

// Directly
const results = testData.filter(greaterThanAge(32));
Enter fullscreen mode Exit fullscreen mode

Writing the filterConstuctor function

There are two ways (at least) we can write this function, the long specific way and the short generic way. We will explore both to gain a better understand of how it works.

Mk 1 - Filter constructor forms
Long specific form

function filterConstuctor(compareFn) {
  return function getProperty(prop) {
     return function getSearchTerm(searchTerm) {
       return (item) => compareFn(item[prop], searchTerm);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Using arrow functions can actually make it more readable.
Short specific form

function filterConstuctor(compareFn) {
  return (prop) => (searchTerm) => (item) => 
    compareFn(item[prop], searchTerm);
}
Enter fullscreen mode Exit fullscreen mode

Short generic form

const filterConstuctor = curry(uncurriedFilterPredicate);

function uncurriedFilterConstuctor(compareFn, prop, searchTerm) { 
  return item => compareFn(item[prop], searchTerm);
}
Enter fullscreen mode Exit fullscreen mode

The crux of this post is how to write the curry function. Of course you are unlikely to do this yourself, but it is useful to know how you might. Instead, it is wise to make use of one of the libraries that provide tried and tested functions for this kind of thing such as lodash. Take a look at the lodash page on the curry function.

The curry function

In the following implementations of the curry function we employ a recursive technique within a closure that keeps the code succinct. Each cycle adds the supplied argument(s) to an array. When sufficient arguments have been supplied the original function is called using the expanded array.

Mk 2 - Simple generic function

function curry(fnUncurried) {
  const expectedParameters = fnUncurried.length;
  const actualArguments = [];
  return curriedFunction;

  function curriedFunction(arg) {
    actualArguments.push(arg);
    return (actualArguments.length === expectedParameters) ?
      fnUncurried(...actualArguments) : curriedFunction;
  } 
}
Enter fullscreen mode Exit fullscreen mode

Words of caution

  1. Optional parameters in the uncurried function are not included in the count Function.length so will have to be managed within the function.
  2. The above implementation only accepts one argument at a time. This limitation has been overcome in the following version (Mk 3) using the array rest and spread operations.
  3. The implementation of curry given above needs to be executed every time before the curried function can be reused. In the following version (Mk 4) we address this limitation.

Mk 3 - Multi-arguments generic function

function curry(fnUncurried) {
  const actualArguments = [];
  return curriedFunction;

  function curriedFunction(...args) {
    actualArguments.push(...args);
    return actualArguments.length === fnUncurried.length
      ? fnUncurried(...actualArguments)
      : curriedFunction;
  }
}
Enter fullscreen mode Exit fullscreen mode

Mk 4 - Reusable generic function

function curry(fnUncurried) {
  const actualArguments = [];
  return curriedFunction;

  function curriedFunction(...args) {
    actualArguments.push(...args);
    return actualArguments.length === fnUncurried.length
      ? runFunction()
      : curriedFunction;
  }
  function runFunction() {
    const retVal = fnUncurried(...actualArguments);
    actualArguments.length = 0;
    return retVal;
  }
}
Enter fullscreen mode Exit fullscreen mode

In the examples shown on the lodash page on the curry method you might have noticed that the generated function is not forced in to taking arguments one-by-one but they can be supplied in batches, all at once and even out of sequence. In fact I think the need for a curried function that forces accepting arguments one-by-one, such as in our long-form example (Mk 2), is rare and not the most usable.

So let's now go one step further and support the provision of variable (unlimited) number of arguments with each call. We will not go as far as to support the
provision of arguments out of order.

To finish

We can create a curry function that accepts arguments until a call is made without any, at which point the function is called with all the arguments provided to that point. I cannot thing of a specific use case for this but I think it is a fun academic exercise.

Mk 5 - Unlimited-args generic function

function curry(fnUncurried) {
  const actualArguments = [];
  return curriedFunction;

  function curriedFunction(...args) {
    return args.length
      ? captureArgs(args)
      : runFunction();
  }
  function captureArgs(args) {
    actualArguments.push(...args);
    return curriedFunction;
  }
  function runFunction() {
    const retVal = fnUncurried(...actualArguments);
    actualArguments.length = 0;
    return retVal;
  }
}
Enter fullscreen mode Exit fullscreen mode

Using this form of curry function requires a different way of calling the curried function, as illustrated below.

const results = testData.filter(nameContains('e')());

// and

const filterGreaterThanAge32 = filterGreaterThan('age', 32);

const results = testData.filter(filterGreaterThanAge32());
Enter fullscreen mode Exit fullscreen mode

Conclusion

Partial-application is a very useful technique for reducing the number of arguments that need to be supplied each time the function is called. It is especially useful when you want to supply a call-back function, such as an event handler, sort comparison or map transformation, with data in addition to the parameters the call-back function usually needs.

Currying is built-in to many Function Programming languages such as Haskell but requires additional processing or a library in JavaScript. Its utility in JS is limited but understanding the process and the mechanisms used to create the function are a valuable learning exercise.

Supporting code for this post can be found at JSFiddle.

Top comments (0)