Variadic currying and currying to fixed arity in JavaScript
Different types of currying and how to use them for problem solving
In this article I will tell you about different types of currying in JavaScript. To understand them, you will need to know about classic currying. You can read my article on that topic and then proceed with this one.
I don’t need the whole world, just you
Let’s look at the example of a curry function in JavaScript:
const curry = (fn) =>
function curried(...args) {
const haveEnoughArgs = args.length >= fn.length
const partiallyApplied = (...moreArgs) =>
curried(...args.concat(moreArgs))
if (haveEnoughArgs) return fn(...args)
return partiallyApplied
}
It returns a function that can handle cases like this:
- curry(someFunction)(1)(2)(3)
- curry(someFunction)(1, 2, 3)
- curry(someFunction)(1, 2)(3)
- curry(someFunction)(1)(2)
What would we like to add to this list?
Well, what if we want to call our curried function with less/more arguments than its requiring?
For example, we have a function that can add up numbers infinitely:
const addInfinite = (...args) => args.reduce((acc, cur) => acc + cur, 0)
We want to curry it and then add up numbers one or more at a time from some infinite stream. We don’t know when we’ll run out of numbers to add, but we know that we only need 10 of them. Let’s say, we are trying to compute some statistical indicator in real-time. So we need to update it as soon as possible and 10 data-points will be enough.
Cool. Let’s try it. We curry and feed it our first number from a stream:
curry(addInfinite)(5) // returns 5
Wait, it returned a value instead of a partially applied function. Now we can’t feed it other numbers. But why?
JavaScript registers our function’s arity as 0 since it can take any number of arguments. So, if we curry it with our classic curry function and call it with anything, it returns right away. Even like this:
curry(addInfinite)() // returns 0
How do we fix this?
We can use currying to fixed arity!
Currying to fixed arity returns a curried function that expects a certain number of arguments to return a value.
So now:
curryN(10, addInfinite)(5)(2)(8) // returns partially applied function
curryN(10, addInfinite)(5)(2)(8)(5)(2)(1)(8)(3)(4)(5) // returns 43
Great, we got what we wanted!
Now let’s see how to write this function. Well, it will be easy. Currying to a fixed arity is the same as classic currying, but instead of getting desired arity from the function itself we just set it explicitly.
Basically, classic currying is a special case of currying to fixed arity.
And we already have the code for classic arity. So, we just change it a bit:
const curryN = (n, fn) =>
function curried(...args) {
// compare to n instead of fn.length
const haveEnoughArgs = args.length >= n
const partiallyApplied = (...moreArgs) =>
curried(...args.concat(moreArgs))
if (haveEnoughArgs) return fn(...args)
return partiallyApplied
}
That’s it! We have ourselves a new function that does currying to a fixed arity.
And since we have established classic currying as a special case of that mechanism, we can now rewrite our classic curry function and get rid of code duplication:
export const curry = (fn) => curryN(fn.length, fn)
Nice!
Stop button
Now, let’s look at another problem. Let’s suppose we have this function:
const addFourOrLessNumbers = (a, b, c, d)
=> (a || 0) + (b || 0) + (c || 0) + (d || 0)
It can add up any number of values up to 4. If we call it like this
addFourOrLessNumbers(1, 2)
it will return 3, as expected. JavaScript did its magic and replaced variables ‘c’ and ‘d’ with undefined value. So it’s calculated properly. But now let’s curry it using classic curry function and then call it with same arguments:
curry(addFourOrLessNumbers)(1, 2) // returns function
We expect to get 3, but got a partially applied function. Why? Because curried function expects all of its arguments before it can be executed.
Okay, as we’ve learned above, we can use curryN for this purpose.
curryN(2, addFourOrLessNumbers)(1, 2) // returns 3
It works. But what if we need to call this function with 2, 3 or 4 in the same place depending on some condition. Of course, we can make separate function for each such case using curryN, but it’s not really handy, right? And imagine similar function, but with 10 or more arguments.
const addTwoOrLessNumbers = curryN(2, addTenOrLessNumbers)
const addThreeOrLessNumbers = curryN(3, addTenOrLessNumbers)
const addFourOrLessNumbers = curryN(4, addTenOrLessNumbers)
...
if (someCondition && numbers.length === 2)addTwoOrLessNumbers(...)
else if (someCondition && numbers.length === 3)
addThreeOrLessNumbers(...)
...
Not cool.
So, classic curry can’t help us here, as well as curryN. What do we do?
Has functional world failed us? Should we just accept it, surrender to the harsh reality, rethink our lives and move on?
No! Because we have variadic currying! So we can just do this:
const curriedAddFourOrLessNumbers = curryV(addFourOrLessNumbers)
curriedAddFourOrLessNumbers(1)(3)
if (someCondition)curriedAddFourOrLessNumbers() // returns 4
Cool!
Variadic currying is a currying that allows currying of a variadic function through termination.
Termination in currying is a process of calling a curried function before it received all of its arguments. It works by calling a curried function with an empty argument.
Do not confuse termination and partial application. The latter returns a function with smaller arity while the former returns a value of a function.
Now let’s write variadic currying function:
const curryV = (fn) =>
function curried(...args) {
let shouldRunRightNow = args.length === 0
const haveEnoughArgs = args.length >= fn.length
const partiallyApplied = (...moreArgs) => {
shouldRunRightNow = moreArgs.length === 0 if (shouldRunRightNow) return fn(...args)
return curried(...args.concat(moreArgs))
}
if (haveEnoughArgs || shouldRunRightNow) return fn(...args)
return partiallyApplied
}
As you can see, it’s pretty similar to classic currying, but more powerful.
Variadic currying = classic currying + ability to get value from curried function on demand (termination).
It’s very useful in cases with variadic functions (functions with varying arity).
If the content of this article seems too heavy, you might want to refresh your understanding of classic currying and then return here.
If you have any questions, feel free to leave a comment here or contact me through my website (link in my Medium profile -> “How to contact me?”). I’ll be happy to help you.
You can just use the library
If you want to use these instruments, you don’t need to write all of that by hand, cover it with tests, add documentation and wrap it in a library.
I’ve already done it for you. Here’s the npm package and GitHub.
That’s all!
I hope, this journey was interesting.
If you have any questions, feel free to leave a comment here or contact me through my website (link in my Medium profile -> “How to contact me?”). I’ll be happy to help you.
Thank you for reading!