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;
};

Jonathan D. Johnson

Loving my wife, my sons, and my work. Building codeship by day and packtracker by night, otherwise you can probably find me outside 🧗🏻‍♂️🏕