This article translation blog.jcoglan.com/2011/03/11/…

Above we looked at several examples of Monads and examined their common ground to clarify their underlying design patterns. Before I talk about how it applies to asynchronous programming we need to talk about generics.

Take a look at the list monad we implemented earlier:

var compose = function(f, g) {
  return function(x) { return f(g(x)) };
};

// unit :: a -> [a]
var unit = function(x) { return [x] };

// bind :: (a -> [a]) -> ([a] -> [a])
var bind = function(f) {
  return function(list) {
    var output = [];
    for (var i = 0, n = list.length; i < n; i++) {
      output = output.concat(f(list[i]));
    }
    return output;
  };
}
Copy the code

In our previous example, we implemented a function that takes an HTMLElement element and returns an array of HTMLElements. Note that bind’s function signature is (a -> [a]) -> ([a] -> [a]). That ‘A’ means we can put any type in there, but type A -> [a] means the function must return an array and the array elements must be of the same type as the input parameters. This is not the case. The correct signature is as follows

bind :: (a -> [b]) -> ([a] -> [b])
Copy the code

For example, bind can accept a function of type String ->[HTMLElements] and return a function of type [String] ->[HTMLElements]. Consider the following two functions: the first takes a tagName string and returns all nodes with tag type tagName, and the second takes a node and returns all the class names of that node.

// byTagName :: String -> [HTMLElement]
var byTagName = function(name) {
  var nodes = document.getElementsByTagName(name);
  return Array.prototype.slice.call(nodes);
};

// classNames :: HTMLElement -> [String]
var classNames = function(node) {
  return node.className.split(/\s+/);
};
Copy the code

If we ignore the return list aspect, then we can conform to these two functions and get a function that takes a tagName and returns a className list of all the nodes whose tags are tagNames.

var classNamesByTag = compose(classNames, byTagName);
Copy the code

Of course we have to consider lists, but Monad only cares about lists, not what is stored in them, and we can use it for function composition:

// classNamesByTag :: [String] -> [String]
var classNamesByTag = compose(bind(classNames), bind(byTagName));

classNamesByTag(unit('a'))
// -> ['profile-links', 'signout-button', ...]
Copy the code

Monad is only concerned with ‘… ‘, regardless of the contents of the list, we can also use pipe syntax.

// bind :: [a] -> (a -> [b]) -> [b]
var bind = function(list, f) {
  var result = [];
  for (var i = 0, n = list.length; i < n; i++) {
    result = result.concat(f(list[i]));
  }
  return result;
};

// pipe :: [a] -> [a -> [b]] -> [b]
var pipe = function(x, functions) {
  for (var i = 0, n = functions.length; i < n; i++) {
    x = bind(x, functions[i]);
  }
  return x;
};

// for example
pipe(unit('a'), [byTagName, classNames])
// -> ['profile-links', 'signout-button', ...]
Copy the code

The PIPE function doesn’t care if bind is associated with a list, we can get the broader type signature of pipe:

pipe :: m a -> [a -> m b] -> m b
Copy the code

In this notation, ma represents the wrapper value of a monad. For example, if the input is a List of strings, for List monad m means ‘… ‘and a stands for string. The broader concept of containers becomes even more important when we consider applying these methods to asynchronous programming.

One of the well-known criticisms of Node.js is that it tends to fall into callback hell, where callbacks are nested on top of each other, making code difficult to maintain. There are many approaches to this problem, but I think Monads offers a more interesting approach that emphasizes the need to separate business logic from the code that binds the data.

Let’s consider an artificial case where we have a file called urls.json that contains JSON containing a URL. We need to read the file, get the URL value, use the URL value to make an HTTP request, and print the returned content. Monads encourages us to think in terms of data types and how to control the flow of data through pipes. We can model this using the following pipeline syntax:

pipe(unit(__dirname + '/urls.json'),
        [ readFile,
          getUrl,
          httpGet,
          responseBody,
          print         ]);
Copy the code

Starting with the file pathname (a string), we can trace the flow of data through the pipe:

  • ReadFile receives a String (pathname) and returns a String (file content)
  • GetUrl takes a String (a JSON document) and returns a URI object
  • HttpGet receives a URI object and returns a Response
  • ResponseBody receives a Response and returns a String
  • Print receives a String and returns null

So each ring in the queue generates data for the next bit. But in Node, many of these operations are asynchronous. Instead of CPS(continuation-passing style) and embedded callbacks, how can we modify these functions to indicate that their return value results may still be unknown? By wrapping it in a Promise:

readFile      :: String   -> Promise String
getUrl        :: String   -> Promise URI
httpGet       :: URI      -> Promise Response
responseBody  :: Response -> Promise String
print         :: String   -> Promise null
Copy the code

The Promise Monad needs three things: a wrapper object, a unit function to wrap a value into the wrapper object, and a bind function to help compound those functions. We can implement a promise using Deferrable in js. Class:

var Promise = new JS.Class({
  include: JS.Deferrable,

  initialize: function(value) {
    // if value is already known, succeed immediately
    if (value !== undefined) this.succeed(value);
  }
});
Copy the code

With this class we can solve our problem. Those functions can return a wrapped Promise object.

// readFile :: String -> Promise String
var readFile = function(path) {
  var promise = new Promise();
  fs.readFile(path, function(err, content) {
    promise.succeed(content);
  });
  return promise;
};

// getUrl :: String -> Promise URI
var getUrl = function(json) {
  var uri = url.parse(JSON.parse(json).url);
  return new Promise(uri);
};

// httpGet :: URI -> Promise Response
var httpGet = function(uri) {
  var client  = http.createClient(80, uri.hostname),
      request = client.request('GET', uri.pathname, {'Host': uri.hostname}),
      promise = new Promise();

  request.addListener('response', function(response) {
    promise.succeed(response);
  });
  request.end();
  return promise;
};

// responseBody :: Response -> Promise String
var responseBody = function(response) {
  var promise = new Promise(),
      body    = '';

  response.addListener('data', function(c) { body += c });
  response.addListener('end', function() {
    promise.succeed(body);
  });
  return promise;
};

// print :: String -> Promise null
var print = function(string) {
  return new Promise(sys.puts(string));
};
Copy the code

At this point we can use the data type to represent a delayed value, with no need for callbacks. Here are the callbacks that implement Unit and bind that are included in these functions. The Unit implementation is simple, just wrapping the value with a Promise. Bind is a bit more complicated: it accepts a Promise object and a function, and it must extract the value from the Promise, pass that value to the function, return another Promise, and wait for the final Promise to complete.

// unit :: a -> Promise a
var unit = function(x) {
  return new Promise(x);
};

// bind :: Promise a -> (a -> Promise b) -> Promise b
var bind = function(input, f) {
  var output = new Promise();
  input.callback(function(x) {
    f(x).callback(function(y) {
      output.succeed(y);
    });
  });
  return output;
};
Copy the code

Using the above definition, we can see that the previous pipeline works, for example, I have put my GitHub API URL in url.json:

pipe(unit(__dirname + '/urls.json'),
        [ readFile,
          getUrl,
          httpGet,
          responseBody,
          print         ]);

// prints:
// {"user":{"name":"James Coglan","location":"London, UK", ...
Copy the code

I hope this illustrates why promises (called Deferred in JQuery and Dojo) are important, how they can help you control the flow of data through pipes and how cleverly the pipes bond. On the client side, we found that the same technique applies to Ajax and animation; In fact MethodChain (which I’ve been using as an asynchronous technique for years) is the Promise Monad, and it applies to method calls rather than function calls.

All of the above code is on my GitHub. Now you can dive into jQuery’s Deferred API. Promise is also designed in Node, but it’s been replaced by CPS, and you’ll find that you can implement these features in the future without going to so much trouble.