My Dad once explained how there are certain things one can live without until one acquires them. A microwave is one such thing. Smart phones, another. The older folks among us will remember a fulfilling life sans internet. For me, currying is among this list.
The concept is simple: You can call a function with fewer arguments than it expects. What is returned is a function that takes the remaining arguments.
You can choose to call it all at once or simply feed in each argument piecemeal.
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
Here we've made a function add
that will take one argument and return a function. By calling it, the returned function remembers the first argument from then on via the closure. Calling it all at once is a bit of a pain, however, so we can use a special helper function called curry
to make defining and calling functions like this easier.
Let's setup a few curried functions for our enjoyment.
var curry = require('lodash.curry');
var match = curry(function(what, x) {
return x.match(what);
});
var replace = curry(function(what, replacement, x) {
return x.replace(what, replacement);
});
var filter = curry(function(f, xs) {
return xs.filter(f);
});
var map = curry(function(f, xs) {
return xs.map(f);
});
The pattern I've followed is a simple, but important one. I've strategically positioned the data we're operating on (String, Array) as the last argument. It will become clear as to why upon use.
match(/\s+/g, "hello world");
// [ ' ' ]
match(/\s+/g)("hello world");
// [ ' ' ]
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]
var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }
censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'
What's being demonstrated here is the ability to "pre-load" a function with an argument or two in order to receive a new function that remembers those arguments.
I encourage you to npm install lodash
, copy the code above and have a go at it in the repl. You can also do this in a browser where lodash or ramda is available.
Currying is useful for many things. We can make new functions just by giving them some arguments as seen in hasSpaces
, findSpaces
, and censored
.
We also have the ability to transform any function that works on single elements into a function that works on arrays simply by wrapping it with map
:
var getChildren = function(x) {
return x.childNodes;
};
var allTheChildren = map(getChildren);
Giving a function fewer arguments than it expects is typically called partial application. Partially applying a function can remove a lot of boiler plate code. Consider what the above allTheChildren
function would be with the uncurried map
from lodash[^note the arguments are in a different order]:
var allTheChildren = function(elements) {
return _.map(elements, getChildren);
};
We typically don't define functions that work on arrays, because we can just call map(getChildren)
inline. Same with sort
, filter
, and other higher order functions[^Higher order function: A function that takes or returns a function].
When we spoke about pure functions, we said they take 1 input to 1 output. Currying does exactly this: each single argument returns a new function expecting the remaining arguments. That, old sport, is 1 input to 1 output. No matter if the output is another function - it qualifies as pure. We do allow more than one argument at a time, but this is seen as merely removing the extra ()
's for convenience.
Currying is handy and I very much enjoy working with curried functions on a daily basis. It is one tool for the belt that makes functional programming less verbose and tedious. We can make new, useful functions on the fly simply by passing in a few arguments and as a bonus, we've retained the mathematical function definition despite multiple arguments.
Let's acquire another essential tool called compose
.
Chapter 5: Coding by Composing
A quick word before we start. We'll use a library called ramda which curries every function by default. Alternatively you may choose to use lodash-fp which does the same and is written/maintained by the creator of lodash. Both will work just fine and it is a matter of preference.
Answers are provided with the code in the repository for this book
var _ = require('ramda');
// Exercise 1
//==============
// Refactor to remove all arguments by partially applying the function
var words = function(str) {
return _.split(' ', str);
};
// Exercise 1a
//==============
// Use map to make a new words fn that works on an array of strings.
var sentences = undefined;
// Exercise 2
//==============
// Refactor to remove all arguments by partially applying the functions
var filterQs = function(xs) {
return _.filter(function(x){ return match(/q/i, x); }, xs);
};
// Exercise 3
//==============
// Use the helper function _keepHighest to refactor max to not reference any arguments
// LEAVE BE:
var _keepHighest = function(x,y){ return x >= y ? x : y; };
// REFACTOR THIS ONE:
var max = function(xs) {
return _.reduce(function(acc, x){
return _keepHighest(acc, x);
}, 0, xs);
};
// Bonus 1:
// ============
// wrap array's slice to be functional and curried.
// //[1,2,3].slice(0, 2)
var slice = undefined;
// Bonus 2:
// ============
// use slice to define a function "take" that takes n elements. Make it curried
var take = undefined;