Last Updated: February 25, 2016
·
1.196K
· dotnetcarpenter

Weave your functions

If you find yourself with functions that calls other functions in a sequence, you'll quickly realize that it's a maintenance nightmare and code reuse is practically non-existing.

tl;dr

If you just want to see a ten line Identity Monad to weave functions together, then scroll down to the bottom.

An example:

readFile('dummy.json');/*{ "text":"...", "url":"http://www.blame.it" }*/
function readFile(filename) {
    getAjax(/* read the file */);
}
function getAjax(json) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', json.url);
    get.onreadystatechange = function() {
        /* check this.readyState */
        if(this.responseText) {
            changeUI(this.responseText);
        }
    }
    xhr.send();
}
function changeUI(data) {
    var dataObject;    
    if(data) {
        try {
            dataObject = JSON.parse(data);
        } catch (e) { return; }
        /* change the UI (usually the DOM) with the new content */
    }
}

Looking at readFile, you'll be forgiven to think that it's purpose is to read a file. In fact, its responsibility is to read a file AND start a chain of functions that will subsequently end with an UI change. Neither readFile or getAjax are reusable. Leading to code duplication.

Callbacks

Now, this can be somewhat mitigated by using callbacks.

readFile(function(json) {
    getAjax(function(data) {
        changeUI(data);
    }, json);
}, 'dummy.json');

function readFile(callback, filename) {
    callback(/* read a file and get an URL */);
}
function getAjax(callback, json) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', json.url);
    get.onreadystatechange = function() {
        /* check this.readyState */
        if(this.responseText) {
            callback(this.responseText);
        }
    }
    xhr.send();
}
function changeUI(callback, data) {
    var dataObject;    
    if(data) {
        try {
            dataObject = JSON.parse(data);
        } catch (e) { return; }
        /* change the UI (usually the DOM) with the new content */
        callback();
    }
}

Now we can re-use all the functions and just provide a callback of our choosing to chain the sequence of functions.

readFile(function(json) { //<- read local file
    changeUI(function() { //<- change UI with local text
        getAjax(function(data) { //<- get data from blame.it
            changeUI(function() {}, data); //<- change UI with remote text
        }, 'http://www.blame.it');
    }, json);
}, 'dummy.json');

But we have to use the convoluted syntax, known as callback hell. Node.js developers should be familiar to the concept.

Monad

What we'll like to do, is to create a composition of functions that we can use and re-use. It turns out, that this is easy to solve with Monads. Monads is a pattern, that separates work flow logic from operation logic, e.i. your functions. This allow you to create compositions of functions you can invoke. I've created a ten lines of code, monadic function, that I call optimist.
jQuery's promise object is in effect, an asynchronously Identity Monad and so is optimist.
Here is how it's used:

optimist(readFile, getAjax, changeUI)('dummy.json');

It takes your functions as arguments and you call it with a seed value, which will be the first argument to the first function.
As optimist creates a composition of functions, we can also pass them around and call them at a later time.

var online = optimist(readFile, getAjax, changeUI);
var offline = optimist(readFile, changeUI);
if(navigator.onLine/*or Ti.network.online if you're using Titanium*/) {
    online('online.json');
} else {
    offline('offline.json');
}
// OR
var updateUI = {
    online: optimist(readFile, getAjax, changeUI),
    offline: optimist(readFile, changeUI)
}
someFunctionThatChoosesASeedFileName(updateUI);

Now, I call this Monad optimist because it assumes that you want to run all supplied functions unless there is an rejection, then it stops. A rejection is implicit, as in if you don't call the callback, it's a rejection. It also assumes that the returned arguments from one function are useful for the next function and doesn't remember arguments passed from earlier functions than the current. Enough chatter, here it is:

var optimist = function() {
    function next(){
        var args = getArguments.call(arguments);
        var currentFn = this.shift();
        if(!currentFn) return;  // done
        currentFn.apply(this, [promise].concat(args));
    }
    function getArguments() {
        return Array.prototype.splice.call(this, 0, this.length);
    }
    var args = getArguments.call(arguments);// you can put this
    var promise = next.bind(args); // inside the bind call
    return promise;
}

The optimist turns all your callbacks into promises, that you can choose to resolve when your operation is done. You don't return a value with the return keyword but by calling the promise with your return value. There is no forking of the chain, just a linear sequence of computations in the future.
Let's break it down.

var optimist = function() {
    ...
    /* turn the supplied functions into an Array */
       var args = getArguments.call(arguments);
   /* bind the Array of functions to the next function,
      which turns the next function into an Array */
    var promise = next.bind(args);
    return promise;
}

When you first call optimist we define promise as the next function with this(the context/scope) set to an Arary of the supplied functions and return that to the consumer of optimist.

var optimist = function() {
    function next(){
        /* get the arguments from the current invoked function
             in the first run, that would be the seed value(s) */
        var args = getArguments.call(arguments);
        /* since the next function is now an Array 
            we can use shift to get the first function
            meaning that next is implemented as a FIFO queue */
        var currentFn = this.shift();
        /* if there is no more functions in the queue we are done */
        if(!currentFn) return;  // done
        /* call the current function with the arguments from the last
            function and prepend the promise */
        currentFn.apply(this, [promise].concat(args));
    }
    ...
}

When you call the result of optimist, with or with-out a seed value, the chain of function invocations begin. The next function is a First In, First Out queue and it's where all the magic happens.
We get the arguments from the current function invocation and assign them to args.
Then we dequeue a function (remember that next is an Array of functions) and call that function with the promise object and args, where promise is a special reference to next, that is bound to the supplied functions. There is no real reason to set the context to this, here. You could set it to currentFn or args. Setting it to thismeans that the functions has access to future functions. Whether or not it's useful, I'm not sure.
The currentFn function will call the promise as its callback and hence begin the next iteration.

The weaved functions can be Object Oriented Programming (OOP) classes. Let me illustrate:

function Person(name) {
    this.name = name;
}
Person.prototype.says = function(callback, to) {
    callback(this.name + " says hi to " + to);
}
var hansel = new Person("Hänsel");
optimist(
    hansel.says.bind(hansel),
    function(greet) {
        console.log(greet);
    }
)("Gretel");

The code

Here is the code in its entirety:

var optimist = function() {
    function next(){
        var args = getArguments.call(arguments);
        var currentFn = this.shift();
        if(!currentFn) return;  // done
        currentFn.apply(this, [promise].concat(args));
    }
    function getArguments() {
        return Array.prototype.splice.call(this, 0, this.length);
    }
    var promise = next.bind(getArguments.call(arguments));
    return promise;
}
// run all functions immediately
optimist(readFile, getAjax, changeUI)('dummy.json');

// create compositions for later use
var online = optimist(readFile, getAjax, changeUI);
var offline = optimist(readFile, changeUI);
if(navigator.onLine/*or Ti.network.online if you're using Titanium*/) {
    online('online.json');
} else {
    offline('offline.json');
}
// OR
var updateUI = {
    online: optimist(readFile, getAjax, changeUI),
    offline: optimist(readFile, changeUI)
}
someFunctionThatChoosesASeedFileName(updateUI);

// run functions and class methods in sequence
function Person(name) {
    this.name = name;
}
Person.prototype.says = function(callback, to) {
    callback(this.name + " says hi to " + to);
}
var hansel = new Person("Hänsel");
optimist(
    hansel.says.bind(hansel),
    function(greet) {
        console.log(greet);
    }
)("Gretel");

function readFile(callback, filename) {
    callback(/* read a file and get an URL */);
}
function getAjax(callback, json) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', json.url);
    get.onreadystatechange = function() {
        /* check this.readyState */
        if(this.responseText) {
            callback(this.responseText);
        }
    }
    xhr.send();
}
function changeUI(callback, data) {
    var dataObject;    
    if(data) {
        try {
            dataObject = JSON.parse(data);
        } catch (e) { return; }
        /* change the UI (usually the DOM) with the new content */
        callback();
    }
}

So far optimist, is only one day old and only used in my CSS white-space proposal to W3C. You can see the impact between white-space.modern.js and white-space.js, where the former uses optimist.
Happy coding!

Further reading:

Video: https://www.youtube.com/watch?v=dkZFtimgAcM