March 20, 2013

For-comprehensions in JavaScript

I’m starting to learn Scala, and I love their for-comprehension style of monadic programming. Here’s a small library that uses eval and textual manipulations to do desugaring.

```// A Monad class for Array and other monads to inherit from

throw "abstract";
}

Monad.prototype.flatMap = function (f) {
return this.map(f).flatten();
};

// Make Array inherit from Monad

// Define the monadic multiplication
Array.prototype.flatten = function () {
var result = [];
var len = this.length;
for (var i = 0; i < len; ++i) {
result = result.concat(this[i]);
}
return result;
};

// The comprehension class
var M = function () {
var pairs = [];
this.for = function (v, e) {
pairs.push({e: e, v: v});
return this;
};
this.yield = function (body) {
var desugared = pairs.map(function (pair, index) {
return '(' + pair.e + ').' +
((index === pairs.length-1) ? 'm' : 'flatM') +
'ap(function(' + pair.v + ') { return ';
}).join('') + '(' + body + ');'
+ pairs.map(function () { return ' });' }).join('');
pairs = [];
return desugared;
};
};

// Avoid the need to say (new M) at the start
M.for = function (v, e) {
return (new M).for(v, e);
};

// Tensor product of lists
// eval(M.for('x','[1,2,3]').for('y','[5,6,7]').yield('x * y'));
// 5,6,7,10,12,14,15,18,21

// Aka Maybe
function Option() {
throw "abstract";
}
Option.prototype.map = function (f) {
if (this instanceof None) { return this; }
return new Some(f(this.value));
};
Option.prototype.flatten = function () {
if (this instanceof None) { return this; }
return this.value;
};
function None() {}
None.prototype = Object.create(Option.prototype);
None.prototype.toString = function () { return 'None'; };
function Some(v) {
this.value = v;
}
Some.prototype = Object.create(Option.prototype);
Some.prototype.toString = function () {
return 'Some(' + this.value + ')';
};

// var xs = [1,2,3];
// eval(M.for('x','xs').for('y','x % 2 ? new Some(x) : new None()').yield('x * y'));
// [Some(1), None, Some(9)]
```

The call to `eval` on the outside is necessary to capture the scope in which the for-comprehension occurs (e.g. the scope in which `xs` occurs in the second example).