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")
);
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")
);
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"
});
});
});
These tests are written in Jest and cover 3 primary cases for our auto-currying function:
- If all values are present will the function run as if it hasn't been curried?
- If I present the values in consecutive function calls, will it return as expected once all necessary values are supplied?
- 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
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);
}
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);
};
}
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
How might you implement this in your language of choice? Let me know in the comments below!
Top comments (10)
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.
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.
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!
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.
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.
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.
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".
Absolutely 😂
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!
I felt the same and actually just ordered food not that long ago 😅