Clojure threading macros in JavaScript
The thread-first and thread-last clojure threading macros are a really neat feature of Clojure, and somewhat difficult to explain to someone who has never had a use for them.
These two tools enable you to unwrap deeply nested function calls in a more readable way, without creating a bunch of single use variables in the process.
I'm going to create a few functions to make this seem more useful...
function sum(a, b) { return a + b; }
function diff(a, b) { return a - b; }
function str(a) { return a + ""; }
Consider this
var result = str(diff(10, sum(3, parseInt("3")))); // "4"
We could make it a bit more clear like this
var inted = parseInt("3");
var sumed = sum(3, inted);
var diffed = diff(10, sumed);
var result = str(diffed); // "4"
But what if we could thread the values through like this? (->>
is the thread-last operator in Clojure)
var result = thread("->>", "3"
parseInt,
[sum, 3],
[diff, 10],
str); // "4"
Looking at this you can start to see what the thread-last macro does. It starts with the value "3"
and passes it as the last argument to parseInt()
. The result of that, gets passed as the last argument to sum()
(with 3 being the first argument). this goes on and on until the last function returns the result.
You can imagine what the thread-first macro (->
) does...
var result = thread("->", "3"
parseInt,
[sum, 3],
[diff, 10],
str); // "-6"
The only call that changes result is diff()
because with the thread-first macro, the result is passed as the first argument. Diff becomes diff(6, 10)
.
Here is my implementation of these Clojure threading macros in Javascript.
var thread = function() {
var i, type, func, value;
var args = Array.prototype.slice.call(arguments);
type = args.shift();
value = args.shift();
switch (type) {
case '->;': // thread-first
while (args.length) {
arg = args.shift();
if (arg instanceof Array) {
func = arg.shift();
arg.unshift(value);
value = func.apply(this, arg);
}
else {
value = arg(value);
}
}
break;
case '->>': // thread-last
while (args.length) {
arg = args.shift();
if (arg instanceof Array) {
func = arg.shift();
arg.push(value);
value = func.apply(this, arg);
}
else {
value = arg(value);
}
}
break;
}
return value;
};