Skip to content

elclanrs/doM.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 

Repository files navigation

doM

"do" monad syntax for JavaScript.
This is just a proof of concept, not to use in production
Demo: http://jsbin.com/omUxotAm/1/edit

Usage

Create your monad as an object, use this as monadic value. For example:

// Maybe monad
var Maybe = {
  unit: function(x) {
    return this.just(x);
  },
  bind: function(f) {
    if (this.isNothing) {
      return this.nothing();
    } else if (this.isJust) {
      return f.call(this, this.x);
    }
  },
  nothing: fluent(function() {
    this.x = null;
    this.isNothing = true;
    this.toString = function() {
      return 'Nothing';
    };
  }),
  just: fluent(function(x) {
    this.x = x;
    this.isJust = true;
    this.toString = function() {
      return 'Just: '+ JSON.stringify(this.x);
    };
  }),
  valueOf: function() {
    return this.x;
  }
};

Then build your functions with the monad:

// Computation that may fail
var add = function(x, y) {
  if (x == null || y == null) {
    return Maybe.nothing();
  }
  return Maybe.just(x + y);
};

And use the do syntax by passing a function to doM:

var result = doM(function() {
  x <- add(1, 2);
  y <- add(x, 2);
  z <- add(x, y);
  return x * y * z;
});

doM will generate and run the following function:

function() {
  return add(1, 2).bind(function(x) {
    return add(x, 2).bind(function(y) {
      return add(x, y).bind(function(z) {
        return this.unit(x * y * z);
      });
    });
  });
}

You can also run code inside each sequence, and access the current monadic value with this:

var result = doM(function() {
  x <- add(1, 2);
  console.log(this.x +' so far');
  y <- add(x, 2);
  console.log(this.x +' so far');
  z <- add(x, y);
  console.log(this.x +' so far');
  return x * y * z;
});
//^ 3 so far
//  5 so far
//  8 so far
//  Maybe {x: 120, ...}

To sequence a monad method with no argument you can simply write:

var result = doM(function() {
  x <- add(1, 2);
  $tell('add 1 plus 2');
  return x;
});

Which is sugar for:

var result = doM(function() {
  x <- add(1, 2);
  _ <- this.tell('add 1 plus 2');
  return x;
});

The result will be the monadic value. toString will give you info about the type. And valueOf will return the value. Note that valueOf can be implicit; here + runs valueOf and returns the value, in this case a number:

result + 2

In ES6 you can use destructuring assignment:

// This works in Firefox today
var result = doM(function() {
  a <- add(1, 2);
  [b, c] <- pair(3, 4);
  return a + b + c;
});

Testing monad laws

var by2 = function(x) {
  if (x == null) {
    return Maybe.nothing();
  }
  return Maybe.just(x*2);
};

var maybe = Maybe.unit(2);

// Left identity
maybe.bind(by2).valueOf() == by2(2).valueOf() // true
// Right identity
maybe.bind(function(x){ return Maybe.unit(x) }).valueOf() == maybe.valueOf() // true
// Associativity
maybe.bind(function(x){ return by2(x).bind(by2) }).valueOf() == maybe.bind(by2).bind(by2).valueOf() // true

Limitations & workarounds

  • Ignores lexical scope. Unfortunately.
  • Function.prototype.toString is not reliable in old browsers.
  • Don't forget the semicolons. doM uses loose regexes to transform the syntax.
  • Only standard // comments are parsed.
  • doM can't be nested, but you can (and should) chain a previous computation of the same monad:
var resultA = doM(function() {
  x <- add(1,2);
  y <- add(x,1);
  return x * y;
});

var resultB = doM(function() {
  x <- resultA;
  return x + 1;
});
  • JSHint will complain about the syntax, but you may try these options:
// Disable warnings

/*jshint expr:true */

// Ignore block

/* jshint ignore:start */
var result = doM(function() {
  ...
});
/* jshint ignore:end */

About

"do" monad syntax for JavaScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published