Currying is a trick in programming where you provide a function with only some of its expected arguments. You then return another function that expects the rest of the arguments, where it will then run the original function with all of them combined.
If you do not provide all the required arguments to a function it is called Partial Application as you have partially applied the arguments to your function, and will need to supply the rest later.
Syntax
To curry a function, all you need to do is return a function variable that will then execute the code with all the supplied arguments. A good example is a greeting message where you can change the greeting word and the users name.
// Here we have a function which returns another function
const greet = greeting => name => `${greeting} ${name}`
// Partially apply the greeting
const sayHello = greet("Hello");
const sayHi = greet("Hi");
// Apply the rest of the params (name)
const helloTim = sayHello("Tim");
const hiTim = sayHi("Tim");
// NOTE: You can also call it all at once
greet("Hello")("Tim")
// Here we have a function which returns another function
const greet = (greeting: string) =>
(name: string) => `${greeting} ${name}`;
// Partially apply the greeting
const sayHello = greet("Hello");
const sayHi = greet("Hi");
// Apply the rest of the params (name)
const helloTim = sayHello("Tim");
const hiTim = sayHi("Tim");
// NOTE: You can also call it all at once
greet("Hello")("Tim")
In this case we hard coded that the greet
function must first be called with the greeting
and then the sayX
functions needed to be called with the name
. The note at the end is valid, but does look a bit funny. Lets see if we can fix it up.
How to automatically curry
Lets say with our greet function we want give developers an option to both use currying and not - ie. with both the following syntax:
greet("Hello", "Tim")
const hello = greet("Hello"); hello("Tim");
Turns out in javascript this is possible! We can do this because functions actually tell you how many parameters they expect! All you need to do is use the length
key of a function. Knowing this you can automatically fire your function once the length is reached.
// Wrapper function
const autoCurry = (func) => {
const numberOfArgs = func.length;
// TODO: Implementation of recursive currying
const handleCurrying = () => {
}
// Setup recursive currying
return handleCurrying();
}
// Wrapper function
const autoCurry = (func: Function) => {
const numberOfArgs = func.length;
// TODO: Implementation of recursive currying
const handleCurrying = () => {
}
// Setup recursive currying
return handleCurrying();
}
Above is a template for what we will use to set up auto currying. We have a function to call to wrap ours, and then a function we can recursively call until we want to execute it, which is yet to be filled in.
So what are the things we need to think about with our recursive function? There are a few things I want it to do:
- Automatically execute the function when the required number of parameters is reached.
- Allow early execution by passing in no parameters.
Lets start with the first task - executing the original function if the number of parameters is reached:
// Lets add a rest operator to put all the params in an array
const handleCurrying = (...args) => {
// Once our length is reached - trigger the function
if (args.length >= numberOfArgs) {
return func(...args);
}
// Return a function to the user which will keep calling
// handleCurrying until the number of args has been met.
return (...newArgs) => {
return handleCurrying(...args, ...newArgs);
}
}
// Lets add a rest operator to put all the params in an array
const handleCurrying = (...args: any[]) => {
// Once our length is reached - trigger the function
if (args.length >= numberOfArgs) {
return func(...args);
}
// Return a function to the user which will keep calling
// handleCurrying until the number of args has been met.
return (...newArgs: any[]) => {
return handleCurrying(...args, ...newArgs);
}
}
And that is all we need to do, but there is a lot happening here so lets look at a few tests.
// Lets just do a simple function that adds 3 numbers
const addThree = (a, b, c) => a + b + c;
// Wrap it with out currying function
const curriedAdd = autoCurry(addThree);
// Testing it out with all parameters
const all = curriedAdd(1, 2, 3) // 6
const partial = curriedAdd(1, 2) // function
const allPartial = curriedAdd(1, 2)(3) // 6
// Lets just do a simple function that adds 3 numbers
const addThree = (a: number,b: number,c: number) => a + b + c;
// Wrap it with out currying function
const curriedAdd = autoCurry(addThree);
// Testing it out with all parameters
const all = curriedAdd(1, 2, 3) // 6
const partial = curriedAdd(1, 2) // function
const allPartial = curriedAdd(1, 2)(3) // 6
So what happened here? Lets go through the tests to see what got called.
I will go through the stack, indenting where the call stack has been added to or we enter a new indent in the code.
Note: The examples have the same initial setup
with the wrapping function, so I will show it once here.
// `addThree` was wrapped by our currying function.
const curriedAdd = autoCurry(addThree);
// the number of required arguments is set to 3
const numberOfArgs = func.length;
// the returned value was the result of calling `handleCurrying`.
return handleCurrying();
// There were no `args` passed in so it skips the args check
if (args.length >= numberOfArgs) {
// the recursive function was returned
return (...newArgs) => {
// `addThree` was wrapped by our currying function.
const curriedAdd = autoCurry(addThree);
// the number of required arguments is set to 3
const numberOfArgs = func.length;
// the returned value was the result of calling `handleCurrying`.
return handleCurrying();
// There were no `args` passed in so it skips the args check
if (args.length >= numberOfArgs) {
// the recursive function was returned
return (...newArgs: any[]) => {
curriedAdd(1,2,3)
The first test provided all three parameters at the same time. Here is the story of what happened.
// Setup from above has already been run
// We call the curried with the following parameters
curriedAdd(1, 2, 3);
// This in turn calls `handleCurrying` with:
// - args = undefined
// - newArgs = [1, 2, 3]
return handleCurrying(...args, ...newArgs);
// Our new `args` variable now equals [1, 2, 3]
const handleCurrying = (...args) => {
// We compare the lengths
if (args.length >= numberOfArgs) {
// They match, so we call the original function
// with the passed parameters
return func(...args);
// addThree is called, where:
// - a = 1
// - b = 2
// = c = 3
const addThree = (a, b, c) => a + b + c;
// Setup from above has already been run
// We call the curried with the following parameters
curriedAdd(1, 2, 3);
// This in turn calls `handleCurrying` with:
// - args = undefined
// - newArgs = [1, 2, 3]
return handleCurrying(...args, ...newArgs);
// Our new `args` variable now equals [1, 2, 3]
const handleCurrying = (...args: any[]) => {
// We compare the lengths
if (args.length >= numberOfArgs) {
// They match, so we call the original function
// with the passed parameters
return func(...args);
// addThree is called, where:
// - a = 1
// - b = 2
// = c = 3
const addThree = (a: number,b: number,c: number) => a + b + c;
curriedAdd(1,2)
The second test provided only two of the required 3 parameters and in the end returned a new function rather than the added result. Here is the story.
// Setup from above has already been run
// We call the curried with the following parameters
curriedAdd(1, 2);
// This in turn calls `handleCurrying` with:
// - args = undefined
// - newArgs = [1, 2]
return handleCurrying(...args, ...newArgs);
// Our new `args` variable now equals [1, 2]
const handleCurrying = (...args) => {
// We compare the lengths but they do not match
if (args.length >= numberOfArgs) {
// the recursive function was returned
return (...newArgs) => {
// Setup from above has already been run
// We call the curried with the following parameters
curriedAdd(1, 2);
// This in turn calls `handleCurrying` with:
// - args = undefined
// - newArgs = [1, 2]
return handleCurrying(...args, ...newArgs);
// Our new `args` variable now equals [1, 2]
const handleCurrying = (...args: any[]) => {
// We compare the lengths but they do not match
if (args.length >= numberOfArgs) {
// the recursive function was returned
return (...newArgs: any[]) => {
Allowing early execution
So our first goal is complete. We can pass our parameters in order and can pass as few or as many as we like. The next step is to allow execution of our function at any point by passing in zero parameters. Before looking below have a think about how this might be achieved. It is done through a simple if statement somewhere.
…
…
…
return (...newArgs) => {
// Check how many params are passed in,
// and if none run the original function
if (newArgs.length === 0) return func(...args);
return handleCurrying(...args, ...newArgs);
};
return (...newArgs: any[]) => {
// Check how many params are passed in,
// and if none run the original function
if (newArgs.length === 0) return func(...args);
return handleCurrying(...args, ...newArgs);
};
The full code
Below is the full code brought together with each part we walked though. 25 lines of code including all the comments. Not too bad.
If you have never used it before - codeclip.io is an interactive tool where you can step through the code! Try using the step options below to see outputs.
What to read more? Check out more posts below!