Sunday, April 14, 2013

The Promise Monad in JavaScript

UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html

If you find it difficult to understand whats going on below, read the following posts.
Implementing Monads in JavaScript
The monad laws and state monad in JavaScript.

We will go through an example of the promise monad in this post. The promise monad is available now in the monadjs library. The best way is too look at an example. We will write a nodejs command line program that will copy an input file into an output file asynchronously. We can run the program like this.

$ node copy.js infile outfile

The program has to do the following.

  1. Check the command line for infile and verify it exists, if not print an error and halt computations.
  2. Check if outfile is given in the command line otherwise print error and halt.
  3. Read infile content into memory and halt if error.
  4. Write content to outfile or print error to console.
  5. Halting of program in case of error to be done without throwing errors.

Computations (1) (3) and (4) are asynchronous. (2) is synchronous because we are only checking process.argv.

The monadic values of the promise monad are functions that take a continuation and promise to call the continuation either asynchronously or synchronously. The continuation is called with a value which is the result of the last computation. If the continuation is called with "null" the computations are halted.

All the computations have access to the results of the previous computations via the "scope" variable. The results are stored in the variable names you give.
Here is the source code of copy.js

var monads = require("monadjs");
var fs = require("fs");

monads.doMonad(monads.promiseMonad,
    "infile",
        function(scope) {
            return function(continuation) {
                var fname = process.argv[2] || "";
                fs.exists(fname, function (exists) {
                    if (exists) {
                        continuation(fname);
                    } else {
                        console.log("File does not exist: " + fname);
                        continuation(null);
                    }
                });
            }
        },
    "outfile",
        function(scope) {
            return function(continuation) {
                var fname = process.argv[3];
                if (fname) {
                    continuation(fname);
                } else {
                    console.log("Output File Name is Required");
                    continuation(null);
                }
            }
        },
    "contents",
        function(scope) {
            return function(continuation) {
                fs.readFile(scope.infile, function (err, data) {
                    if (err) {
                        console.log("Error reading File: " + scope.infile);
                        continuation(null);
                    } else {
                        continuation(data);
                    }
                });
            }
        },
    function(scope) {
        fs.writeFile(scope.outfile, scope.contents, function (err) {
            if (err) {
                console.log("Error writing File: " + scope.outfile);
            }
        });
    }
);

I don't think the promise monad obeys the monad laws. But it works. It works only for sequential asynchronous calls though. What is interesting is that it allows you to break the program structure into bite size pieces and call them sequentially. Notice also the promise monad is implemented purely functionally, and no timing loops used.

However you don't have to actually use the promise monad from this library. I have refactored and simplified everything in a simple promise library you can find here.

No comments:

Post a Comment