I came across currying years ago when I first tried my hand at learning Haskell. Although I understood it, I could not fathom how it could be useful in mainstream imperative languages. This is the case no more and I hope I can help to shine some light on currying and how it can help you (at least when using Javascript).
Curry whaaaaatt?
Let me first just define what currying is. From the Wikipedia article on currying:
currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument
In simpler(ish) terms, currying just means you take a method that receives more than one argument and swap that out for a chain of method calls the each takes one argument. Look at the implementation below (gist):
function sum(a, b) {
return a + b;
}
console.log(sum(3, 5)); // prints 8
The currying version of this is (gist):
function sum(a) {
return b => a + b;
}
console.log(sum(3)(5)); //prints 8
“Charl!” I hear you shout. “That’s just stupid! Why on earth would you do that?!” Hold on, hold on. Remember, I am still just defining it. Give me a little bit more of your time!
To explain the curry example above using English:
sum
takes an argument and returns a function takes a second argument and adds it to the argument received in the first.
We are leveraging Javascript closures here. A more verbose version of the currying sum above is (gist):
function sum(a) {
function sumAWith(b) {
return a + b;
}
return sumAWith;
}
console.log(sum(3)(5)); //prints 8
But, but… how on earth is that useful?
Let me show you now how currying can be used in real code to give you real benefit.
Let us build an advanced filter function called advancedFilter
(I know, original, huh?). The function will take two arguments. The first argument, collection
, will be an array containing objects. The second argument, match
, will be just an object. The function must filter out all objects in collection
that do not have matching properties (and their associated values) in match
.
So if we want to filter a collection like [ { name: "apple", color: "red" }, { name: "pear", color: "yellow" } ]
and we are interested in only red fruit. We would call our method with the aforementioned fruit collection and [ { color: "red } ]
as the second argument. advancedFilter
should then match only the apple, and return that in an array.
The following code is a reasonable expectation of a solution you can expect to see to the problem above (gist).
function advancedFilter(collection, match) {
const keysOf = obj => Object.keys(obj)
const toKeyValue = (obj, key) => ({key, value: obj[key]});
const matchKeyValue = (keyValue1, keyValue2) =>
keyValue1.key === keyValue2.key &&
keyValue1.value === keyValue2.value;
const matchOnAll = (keyValues, keyValue) =>
keyValues.some(item => matchKeyValue(keyValue, item))
// end functions
const matchKeyValues = keysOf(match).map(item => toKeyValue(match, item));
return collection.reduce((acc, cur) => {
const curKeyValues = keysOf(cur).map(item => toKeyValue(cur, item));
return matchKeyValues.every(item => matchOnAll(curKeyValues, item))
? [...acc, cur]
: acc
}, []);
}
You can test out if this works on codepen.io.
The algorithm first maps the match
argument to an array of key-value pairs, matchKeyValues
. Then we reduce over collection
by:
- Mapping each object
cur
to key-value pairs,curKeyValues
. - Adding
cur
to the returned array only if every key-value pair inmatchKeyValues
exists incurKeyValues
.
The next code block is the same function but changed subtly to leverage currying to make the algorithm more readable (gist).
function advancedFilter(collection, match) {
const keysOf = obj => Object.keys(obj)
const toKeyValue = obj => key => ({key, value: obj[key]});
const matchKeyValue = keyValue1 => keyValue2 =>
keyValue1.key === keyValue2.key &&
keyValue1.value === keyValue2.value;
const matchOnAll = keyValues => keyValue =>
keyValues.some(matchKeyValue(keyValue))
// end functions
const matchKeyValues = keysOf(match).map(toKeyValue(match));
return collection.reduce((acc, cur) => {
const curKeyValues = keysOf(cur).map(toKeyValue(cur));
return matchKeyValues.every(matchOnAll(curKeyValues))
? [...acc, cur]
: acc
}, []);
}
You can test out if this works on codepen.io.
The utility functions have changed slightly to accept a single argument and return a function that matches the expected function signature of array functions like every
, some
and filter
. It is the immediately evaluated and the return function passed on to the array function. Javascript closures allow the array function to call the provided callback and still have access to the first argument used (or how many ever was in the call chain).
Readability
In the second version, the algorithm has suddenly become more readable. We did this by taking away parameter noise on the array functions we’re using. Here are some side by side comparisons:
The statements to my eyes now read more naturally. For example:
every matchKeyValues should matchOnAll curKeyValues
Abstraction and reusability
Other than the readability benefit, you also increase the abstraction and re-usability of your functions. Of course, that is only true if the local
functions here were extracted and imported for use. The reason why your re-useability goes up is that you can chain these abstractions in many different novel ways. With multi-argument functions, you are generally going be restricted to more specific use cases.
Conclusion
I am by no means encouraging you to go and slash all arguments but one from your functions. Far from it. The use case here is very much aimed at reducing the complexity contained in array function callbacks, and it is an easy and natural fit.
Be on the lookout for where the use of currying can improve your code quality, and I hope this article helps you in that endeavour just a little.