Currying is a great tool that allows you to split arguments into individual functions. The main advantage is that it allow you to specialize or partially apply functions to then be passed to higher order functions, like map() and reduce().
Today, we’ll show you how to use currying
in your own programs.
We’ll cover:
Learn functional programming with hands-on practice.
Functional Programming Patterns With RamdaJS
The definition of currying is:
Currying turns multi-argument functions into unary (single argument) functions.
Curried functions take many arguments one at a time.
In other words, In other words, the function takes the first argument and returns a new function. This new function takes the second argument and returns a new function which then takes the third argument. This repeats until all arguments have been fulfilled.
Currying is helpful for functional programming as it allows a short hand syntax for this common functional procedure.
Let’s see an example:
greet = (greeting, first, last) => `${greeting}, ${first} ${last}`;greet('Hello', 'Bruce', 'Wayne'); // Hello, Bruce Wayne
Properly currying greet
gives you:
curriedGreet = curry(greet);curriedGreet('Hello')('Bruce')('Wayne'); // Hello, Bruce Wayne
This 3-argument function has been turned into three unary functions. Currying returns a function for each passed parameter.
I say “properly currying” because some curry
functions are more flexible in their usage. Currying’s great in theory, but invoking a function for each argument gets tiring in JavaScript.
Ramda’s curry
function lets you invoke curriedGreet
like this:
// greet requires 3 params: (greeting, first, last)// these all return a function looking for (first, last)curriedGreet('Hello');curriedGreet('Hello')();curriedGreet()('Hello')()();// these all return a function looking for (last)curriedGreet('Hello')('Bruce');curriedGreet('Hello', 'Bruce');curriedGreet('Hello')()('Bruce')();// these return a greeting, since all 3 params were honoredcurriedGreet('Hello')('Bruce')('Wayne');curriedGreet('Hello', 'Bruce', 'Wayne');curriedGreet('Hello', 'Bruce')()()('Wayne');
Notice you can choose to give multiple arguments in a single shot. This implementation’s more useful while writing code.
And as demonstrated above, you can invoke this function forever without parameters and it’ll always return a function that expects the remaining parameters.
Mr. Elliot shared a curry
implementation much like Ramda’s. Here’s the code, or as he aptly called it, a magic spell:
const curry = (f, arr = []) => (...args) =>((a) => (a.length === f.length ? f(...a) : curry(f, a)))([...arr, ...args]);
It’s incredibly concise, so let’s refactor and appreciate it together.
The version below works the same as the dense implementation above.
I’ve also sprinkled debugger
statements to examine it in Chrome Developer Tools.
curry = (originalFunction, initialParams = []) => {debugger;return (...nextParams) => {debugger;const curriedFunction = (params) => {debugger;if (params.length === originalFunction.length) {return originalFunction(...params);}return curry(originalFunction, params);};return curriedFunction([...initialParams, ...nextParams]);};
Open your Developer Tool sand follow along!
To get started, paste greet
and curry
into your console. Then enter curriedGreet = curry(greet)
to begin.
Inspecting our two parameters we see originalFunction
is greet
and initialParams
defaulted to an empty array because we didn’t supply it.
curry(greet)
just returns a new function that expects 3 more parameters. Type curriedGreet
in the console to see the the new function.
Now that we’ve seen the basic function, let’s get a bit more advanced and dosayHello = curriedGreet('Hello')
.
To see the original function and parameters, type originalFunction
and initialParams
in your console.
Notice we can still access those 2 parameters even though we’re in a completely new function? That’s because functions returned from parent functions enjoy their parent’s scope.
After a parent functions can leave their parameters for their child functions to use. Kind of like inheritance in the real life sense.
curry
was initially given originalFunction
and initialParams and then returned a “child” function.
Those 2 variables are not disposed in case the child function needs to reference them. If they don’t, then that scope gets cleaned up and the parameters are deleted.
On line 4, put a debugger
to check the status of the program. This will help us understand how curry
handles parameters.
Inspect nextParams
and see that it’s ['Hello']
…an array? But I thought we said curriedGreet(‘Hello’)
, not curriedGreet(['Hello'])
!
Correct: we invoked curriedGreet
with 'Hello'
, but thanks to the REST syntax, 'Hello'
becomes into ['Hello']
.
This is because curry
is a general function that can be supplied 1, 10, or 10,000,000 parameters, so it needs a way to reference all of them. Using the REST syntax like that captures every single parameter in one array, making curry
’s job much easier.
Let’s jump to the next debugger
statement.
You may have noticed that line 12 actually ran before the debugger
statement on line 6. Our program defines a function called curriedFunction
on line 5, uses it on line 12, and then we hit that debugger
statement on line 6.
What is curriedFunction
invoked with?
[...initialParams, ...nextParams];
Look at params
on line 5 and you’ll see ['Hello']
. Both initialParams
and nextParams
were arrays, so we flattened and combined them into a single array using the handy spread operator (...
).
Take a look:
Line 7 says “If params
and originalFunction
are the same length, call greet with our params and we’re done.”
This is how curry
does its magic! Curry
uses the output from .length()
to know when it must ask for more parameters.
In JavaScript, a function’s .length
property tells you how many arguments it expects. If there are less arguments (lower length) than expected, curry
will ask for more arguments.
greet.length; // 3iTakeOneParam = (a) => {};iTakeTwoParams = (a, b) => {};iTakeOneParam.length; // 1iTakeTwoParams.length; // 2
If our provided and expected parameters match, we’re good, just hand them off to the original function and finish the job!
Our example has less than the desired number of arguments.
We only provided ‘Hello’
, so params.length
is 1, and originalFunction.length
is 3 because greet
expects 3 parameters: greeting, first, last
.
Since that if
statement evaluates to false
, the code will skip to line 10 and re-invoke our master curry
function. It re-receives greet
and this time, 'Hello'
, and begins all over again.
curry
is essentially an infinite recursive loop of self-calling, parameter-hungry functions that won’t rest until the function has enough parameters.
Same parameters as before, except initialParams
is ['Hello']
this time. Skip again to exit the cycle. Type our new variable into the console, sayHello
. It’s still expecting more parameters but we’re getting closer to our exit condition.
Next we’ll try it with sayHelloToJohn = sayHello('John')
.
We’re inside line 4 again, and nextParams
is ['John']
. Jump to the next debugger on line 6 and inspect params
: it’s ['Hello', 'John']
!
Line 12 says “Hey curriedFunction
, he gave me 'Hello'
last time and ‘John’
this time." It then combines both into this array using the spread operator, [...initialParams, ...nextParams]
.
Now curriedFunction
again compares the length of these params
to originalFunction
, and since 2 < 3
we move to line 10 and call curry
once again! And of course, we pass along greet
and our 2 params, ['Hello', 'John']
.
Next we pass the final parameter:
sayHelloToJohnDoe = sayHelloToJohn('Doe')
This is added to the array with the previous parameters. By this point, we have the 3 we need and our exit condition triggers.
greet
got his parameters, curry stopped looping, and we’ve received our greeting: Hello, John Doe
.
Play around with this function some more. Try supplying multiple or no parameters in one shot, get as crazy as you want. See how many times curry
has to recurse before returning your expected output.
curriedGreet('Hello', 'John', 'Doe');curriedGreet('Hello', 'John')('Doe');curriedGreet()()('Hello')()('John')()()()()('Doe');
Many thanks to Eric Elliott for introducing this to me, and even more thanks to you for appreciating curry
with me. Until next time!
If you’d like more information on currying, you can visit Educative’s course Functional Programming Patterns with RamdaJS which gives you an in-depth look at currying, its advantages, and much more.
Happy learning!
Free Resources