Vindaloo Javascript
Introduction
Functions are fun. In most languages, you can pass them as a variable. You can declare them anonymously (lambdas), you can close them over variables (closures).
In some languages, you can even execute them partially. Take a look at this example, in Purescript:
concat :: String -> String -> String
concat a b = a <> " " <> b
concatthe :: String -> String
concatthe = concat "the"
concatthe
is a function which takes a single string variable and returns concat
applied to the single parameter "the". This means that it returns a function that takes a single parameter, and applies it to concat as the /second/ parameter, the first one already being "the".
So the concatthe
function could also be written as:
concatthe :: String -> String
concatthe b = concat "the" b
You can see in this version, that the call to concat
has got two parameters, which is the right amount for concat
.
You can do this with an arbitrarily long sequence of parameters, by the way. But only so long as you give the function the parameters in order.
The fact that partially applying function returns another function which takes the remainder of the arguments is called currying, after Haskell Curry, who was a hugely awesome dude and all round straight up gent.
Currying in Javascript
Now of course, as you immediately grokked (not like me), this currying has to do with the fact that Purescript is purely functional, and so the compiler is effectively resolving an equation, which is why it can deal with missing variables, so long as they resolve at some point in the equation (hence the partial application), and it can figure out which parameters are still missing (hence having to supply them in order).
In Javascript, things are different. Although you can pass functions around until you execute them, you must pass all variables to a function when it is called. So in Javascript, the preceding code would look like this:
concat = ( a ) => {
return ( b ) => {
return a + " " + b;
}
}
concatthe = ( b ) => {
return concat ( "the" ) ( b {;
}
This works the same, but as you can see, you have to do it yourself. This means of course, that along with the fact that currying takes a lot of extra code, if you curry a function that already exists, you have to refactor all the existing code which calls that function, which sucks a lot.
Abusing language features for fun and profit
But what if you could curry in Javascript? It turns out there is a way, and what is more, you can curry without having to pass the parameters in order.
Let's return to our concat
function.
Instead of passing two parameters, a
and b
, we can pass a javascript object with two keys, a
and b
.
So our function signature is:
concat = ( params ) => ???
and the body of the function will extract a
and b
concat = ( params ) => {
return params.a + " " + params.b;
}
This version is semantically equivalent to the simple version with two parameters. Let's make it check for the existence of a
and b
.
concat = ( params ) => {
if ('a' in params && 'b' in params) {
return params.a + " " + params.b;
}
return "???"; // In the two-parameter version, this is a runtime error: a parameter is missing
}
This works, but what we are trying to do is make a function that can accept one or the other of its parameters while still remaining executable. So what we should return is another function that expects the missing parameter. Let's try to do that.
concat = ( params ) => {
if (!('a' in params)) {
if (!('b' in params)) {
return '';
}
return ( a ) => {
return a + "" + params.b;
}
}
if (!('b' in params)) {
if (!('a' in params)) {
return '';
}
return ( b ) => {
return params.a + "" + b;
}
}
return params.a + " " + params.b;
}
Ha ha ha. I didn't even try to run that. We need to make this more versatile if we want to have any hope of generalising it. This leads to the next try, which is a bit of a leap. Make sure you understand this next function before moving on.
function concat(params, prev = {}) {
var parameter_names = [ 'a', 'b' ];
var fulldata = { ...params, ...prev };
if (parameter_names.reduce((acc, val) => {
return acc && (val in fulldata);
}, true) === true) {
return fulldata['a'] + ' ' + fulldata['b']);
} else {
return function(params_) { concat(params_, fulldata); };
}
}
So, what is going on here?
First, we have added a list that holds all of the names of our parameters.
Second, we have added second parameter, prev
, but it has a default value, so you do not need to actually pass it for the function to work.
Now for the body of the function.
The first thing we do after declaring our list of parameters, is to map both the params, and the prev data structure into one: fulldata
, so that we have all the passed parameters, however far down the recursion stack we might be.
Then we check the full list of parameters using reduce
, checking to see if each parameter is present. If this is the case, we execute our original code.
If not, we return a function that, when passed some more parameters, recursively calls the external function, but also passes, via the prev
parameter, all the parameters that we have received so far. You should think about this for a minute, and try to prove to yourself how this makes the whole thing work. All the parameters of all the previous invocations of the function are saved here (although redefining a parameter only keeps the latest value).
And there it is. Named value currying, or vindaloo currying, as I shall be calling it.
There is of course, as you have no doubt already figured out, a slight problem, in that it is a lot of code. Well, we could make it so that this function can work with any other function, then we don't have to write out the recursive bits every time.
I'm just going to show you the code. You should be able to figure it out by now.
function vindaloo(fct, parameter_names) {
inner = (params, prev = {}) => {
var fulldata = { ...params, ...prev };
if (parameter_names.reduce((acc, val) => {
return acc && (val in fulldata);
}, true) === true) {
return fct(fulldata);
} else {
return function(params_) { inner(params_, fulldata); };
}
}
return inner;
}
Now we can write something like this:
concat = vindaloo(p => "${p.a} ${p.b}", ["a", "b"]);
and use it like so:
concat1 = concat({a: 'hello'});
concat1({b: "world"});
This works, but is still a bit clunky. There is a feature proposal for ECMAScript which introduces a new syntax for partial function application via the ?
keyword. Check it out if this post made you curious!