DEV Community

loading...
Cover image for Currying

Currying

James Robb
I am a Polyglot Software Engineer focussed on the web, an ardent Accessibility Advocate, amateur creative coder and an aspiring teacher.
Updated on ・4 min read

In mathematics and computer science, currying is a way of splitting a function which takes multiple arguments into a sequence of functions that take a subset of those arguments until all are provided and can be used to execute the original function.

For example if we had a function like this:

function createPerson(name, age, occupation) {
  return {
    name,
    age,
    occupation
  };
}

console.log(
  createPerson("James", 26, "Senior Software Engineer")
);
Enter fullscreen mode Exit fullscreen mode

We could also make something like this to generate the same output:

function createPerson(name) {
  return function(age) {
    return function(occupation) {
      return {
        name,
        age,
        occupation
      };
    }
  }
}

console.log(
  createPerson("James")(26)("Senior Software Engineer")
);
Enter fullscreen mode Exit fullscreen mode

This is useful in scenarios where you only have part of the data available for execution and wish to partially apply incoming data until all requirements are fulfilled. This is what makes currying and partial application two very related subject matters.

In this article we will write a higher order function to auto-curry any other function for us to avoid nested return function statements like we saw in the example above.

Tests

function add(left, right) {
  return left + right;
}

function createPerson(name, age, occupation) {
  return {
    name,
    age,
    occupation
  };
}

describe("Curry", () => {
  it("Should throw for invalid parameters", () => {
    expect(() => curry("string")).toThrowError(TypeError);
  });

  it("Should run as normal if all arguments are presented", () => {
    const curriedAdd = curry(add);
    const sum = curriedAdd(1, 2);
    expect(sum).toBe(3);
  });

  it("Should run if values are passed in via consecutive calls", () => {
    const curriedAdd = curry(add);
    const sum = curriedAdd(1)(2);
    expect(sum).toBe(3);
  });

  it("Should run if values passed in consecutive calls of varying length", () => {
    const curriedCreatePerson = curry(createPerson);
    const person = curriedCreatePerson("James")(26, "Senior Software Engineer");
    expect(person).toEqual({
      name: "James",
      age: 26,
      occupation: "Senior Software Engineer"
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

These tests are written in Jest and cover 3 primary cases for our auto-currying function:

  1. If all values are present will the function run as if it hasn't been curried?
  2. If I present the values in consecutive function calls, will it return as expected once all necessary values are supplied?
  3. If I have multiple parts of data available to apply, can I do so without more function calls than is necessary?

In short, you can call the curried version of the function as often or as little as you like so long as the values from each call equate eventually to a minimum of those required by the function that was curried.

Implementation

This implementation will introduce some oddities of the JavaScript language, specifically that when it comes to functions, JavaScript allows us to access a length property representing the amount of arguments required for that function.

For example if I were to run the following:

function add(left, right) {
  return left + right;
}

console.log(add.length); // 2
Enter fullscreen mode Exit fullscreen mode

We would see an output to the console of 2 since we require the left and right parameters to be present.

This is interesting and weird in my opinion although I am sure some others think this is totally a normal feature to have but I don't know... Anyway, I recommend reading the MDN article on the function length property to understand why and how this works.

For our curry higher order function function we use this property which is why I thought it relevant to bring up for those who may not be aware of it.

Anyhow, without further ado, here is the implementation I have chosen to go with for the curry higher order function:

/**
 * @function curry
 * @description A function which can curry any another function
 * @see {@link https://en.wikipedia.org/wiki/Currying Wikipedia article about currying}
 * @param {Function} fn - The functions to curry
 * @returns {(Function|*)} A function to apply the remaining required aruments to or the return value of the provided `fn` when enough arguments are eventually provided
 */
function curry(fn, ...parameters) {
  if (typeof fn !== "function") {
    throw new TypeError(`Parameter 1 must be of type Function. Received: ${typeof fn}`);
  }

  if (parameters.length >= fn.length) {
    return fn(...parameters)
  }

  return (...args) => curry(fn, ...parameters, ...args);
}
Enter fullscreen mode Exit fullscreen mode

In short, our implementation utilises the length propery to check if the parameters provided to the curry function equate to that of the requirements of the fn function. If the requirements were fulfilled, it runs the fn function with the parameters that were provided and returns it's value.

If on the other hand there are not enough parameters available, a new function is returned which takes a set of args. When this new function is executed, we recursively run the curry function again passing in any previously provided parameters and the new incoming args as the new iterations value of the parameters argument. From here the cycle either breaks or is recursed again until all values that are required are provided and the fn that was originally provided can be executed and it's return value returned.

Conclusions

Currying is a relatively simple thing once you wrap your head around it and plays a relatively big role in Functional Programming due to it's ability to easily achieve partial application and split larger actions into smaller ones.

Currying is also a relatively common concept and so understanding how it works in your language of choice won't hurt, for example in PHP I would rewrite the JavaScript implementation above, like so:

function curry(string $fn, ...$parameters) {
  $reflector = new ReflectionFunction($fn);
  $requiredArgs = $reflector->getNumberOfParameters();

  if(count($parameters) >= $requiredArgs) {
    return call_user_func($fn, ...$parameters);
  }

  return function(...$args) use ($fn, $parameters) {
    return curry($fn, ...$parameters, ...$args);
  };
}
Enter fullscreen mode Exit fullscreen mode

The code is almost identical but we need to use a few different language specific features such as the ReflectionFunction class that PHP gives us or that functions are passed as string references for example but generally speaking this is the same code with only some minor differences. We can even run the PHP example basically the same way we do the JavaScript version, like so:

function add($left, $right) {
  return $left + $right;
}

$curriedAdd = curry("add");
$result = $curriedAdd(1)(2);
echo $result; // 3
Enter fullscreen mode Exit fullscreen mode

How might you implement this in your language of choice? Let me know in the comments below!

Discussion (10)

Collapse
gregfletcher profile image
Greg Fletcher

Nice! Do you often use functional programming in your normal daily programming?
I personally prefer functional programming when possible but I'm always interested to hear what people like about it For me, a couple of the benefits of functional programming are the readability of the code and also the removal of boilerplate.

Collapse
jamesrweb profile image
James Robb Author

I try to use it as often as possible but working with different clients means that sometimes their systems are in OOP languages or some other paradigm and so using FP isn't always possible. It is something I find easier to reason with though personally since I aim to break any task down to the basics and this maps well with FP in my opinion. All I need to do is compose each task/action together to have the desired outcome and so using something like the compose function or pipe function is very useful in such scenarios.

Collapse
wrldwzrd89 profile image
Eric Ahnell

I am very interested in adopting functional programming, for one simple reason: functions, being free of side effects, are easier to reason about and verify. The type of things I create benefit greatly from this attribute!

Collapse
jamesrweb profile image
James Robb Author

Agreed, it can give you a lot of freedom but other paradigms bring benefits of their own, it's really a balance always but if you keep functions simple and work them together logically it can work out pretty well for all involved.

Collapse
wrldwzrd89 profile image
Eric Ahnell

I agree! I don’t think switching exclusively is beneficial enough to do for exactly the reasons you stated, but I would benefit from using the ideas where they make sense.

Thread Thread
jamesrweb profile image
James Robb Author

Exactly and if you can break all tasks into small pieces, there's no reason why all your work couldn't just be function compositions but of course we don't live in such an ideal world although in a lot of cases it is feasible to do.

Collapse
eljayadobe profile image
Eljay-Adobe

I had commented on another currying article on Dev.to over here, dev.to/eljayadobe/comment/o3p

Currying in JavaScript is "meh".
Currying in C++ is "omg, ow ow ow".
Currying in F# is "ahhh, nerdvana".

Collapse
jamesrweb profile image
James Robb Author

Absolutely 😂

Collapse
thatonejakeb profile image
Jacob Baker

Slightly off-topic, but it is way to close to dinner time for this to keep going to the top of my feed and showing me a huge picture of a curry!

Collapse
jamesrweb profile image
James Robb Author

I felt the same and actually just ordered food not that long ago 😅