2

As I learn about monads after reading dozens of tutorials I'm trying to implement the pattern in JavaScript. I'm using LiveScript and Prelude to translate some Haskell monad examples.

So the monad I'm trying to implement as an exercise is the List monad. I wrote the following, in LiveScript:

List = do ->
  # unit :: a -> ma
  unit = (a) -> [a]
  # bind :: ma -> (a -> mb) -> mb
  bind = (ma, f) --> concat (map f) ma
  # lift :: (a -> b) -> ma -> mb
  lift = (f, ma) --> bind ma, (a) -> unit f a

  {unit, bind, lift}

add1 = (x) -> x+1

let {unit, bind} = List
  x <- bind [1]
  y <- bind [2]
  z <- bind [3]
  unit add1 x+y+z #=> [7]

(List.lift add1) [1 2 3] #=> [2 3 4]

The LiveScript syntax to nest functions at the same level of indentation is quite handy, but it translates to a JavaScript callback hell obviously:

List = function(){
  var unit, bind, lift;
  unit = function(a){
    return [a];
  };
  bind = curry$(function(ma, f){
    return concat(map(f)(ma));
  });
  lift = curry$(function(f, ma){
    return bind(ma, function(a){
      return unit(f(a));
    });
  });
  return { unit: unit, bind: bind, lift: lift };
}();
add1 = function(x){
  return x + 1;
};
(function(arg$){
  var unit, bind;
  unit = arg$.unit, bind = arg$.bind;
  bind([1], function(x){
    return bind([2], function(y){
      return bind([3], function(z){
        return unit(add1(x + y + z));
      });
    });
  });
}.call(this, List));
List.lift(add1)([1, 2, 3]);

What I want is to implement the pattern on the receiver to be able to use it like so:

List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z });

After watching this video where Crockford explains monads in JavaScript (code), I understand that the MONAD he presents is just an object with methods that could be also be implemented with prototypes. The unit method is the constructor and bind is an instance method that runs a function on the value with the given arguments. Then lift adds a new method to the prototype that runs the given function on the monadic value.

But, is it an actual monad or a monadic pattern like jQuery? I have doubts about this particular explanation of monads because there is no computation sequence, the bind method runs the function right away and returns a new instance of the "monad", it is not composition, as in the monad I implemented in LiveScript which was based on the Haskell examples.

So my question is:

  1. Is my implementation of monad correct?
  2. Is Crockford's implementation of monad correct?
Community
  • 1
  • 1
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • I'm not sure what `List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z });` is supposed to do. `bind` does usually take a function (which produces a list)? – Bergi Nov 26 '13 at 00:11
  • I think that's the part I don't quite understand yet, is how to make the monad work on the receiver but still use function composition. How would it have to look like with my example then? – elclanrs Nov 26 '13 at 00:50
  • You have three monad instances there so it won't be easy. Those LiveScript backcalls, just as Haskells do-notation, is just syntactical sugar for the *necessary* `lift` callback hell. You still will need to write `List([1]).bind((x)->List([2]).bind((y)->List([3]).bind((z)->List.unit(x+y+z))))`. Surely you could implement some sugar function for that, in this case [`lift3`](http://hackage.haskell.org/package/base-4.6.0.1/docs/Control-Monad.html#v:liftM3). – Bergi Nov 26 '13 at 02:55
  • I was trying to gather the values and make a dynamic binding function then in the `do` method I would `foldl (<<) fns` to compose them, but didn't work. I see what you mean, you have to pass the functions anyway. I understand that with the single `lift` function I can simply do `unit 1 |> lift add 2 |> lift add 3 |> lift add1 #=> [7]`, in LiveScript, but it would still look like a mess in JS. So I understand that functions must be of one argument and I must create many `liftX` depending on how many instances I have? – elclanrs Nov 26 '13 at 03:08
  • Yeah, if expressed in terms of `lift` it will always be a "mess" in JS. To make it better (and more performant), you'd use a comprehension: `concat(for x in [1] for y in [2] for z in [3]: x+y+z)`. Another idea: due to JavaScript's lax typing it should be possible to implement a true generic (recursive?) `liftN` function with variadic arguments if you want. – Bergi Nov 26 '13 at 03:20
  • Yeah, comprehension for list monad will do. I actually tried implementing other monads with comprehensions but it looked like a mess in LiveScript too. _"true generic recursive lift"_ -- This sounds exactly like what I want. I will ditch Crockfords implementation, and give it a try. This will keep me busy (until my next question...) – elclanrs Nov 26 '13 at 03:26

1 Answers1

3

Is my implementation of monad correct?

Yes, your unit and bind functions do what is expected from the List monad.

However, the second line, List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z }); does not look like a monad at all.

Those LiveScript backcalls, just as Haskell's do-notation, is just syntactical sugar for the necessary lift callback hell. You still will need to write:

List([1]).bind((x)->List([2]).bind((y)->List([3]).bind((z)->List.unit(x+y+z))))‌

If expressed in terms of bind it will always be a "mess". To make it better (and more performant), you'd use a list comprehension:

concat(for x in [1] for y in [2] for z in [3]: x+y+z)

Another idea: due to JavaScript's lax typing it should be possible to implement a true generic (recursive?) liftN function with variadic arguments if you want:

function lift(n, fn) {
    var argsPos = 2;
    if (typeof n != "number") {
        fn = n;
        n = fn.length;
        argsPos--;
    }
    var args = [].slice.call(arguments, argsPos);
    if (n < args.length) // curry
        return function(){
            return lift.apply(null, [n, fn].concat(args, [].slice.call(arguments)));
        }
    return (function bindr(bound, args)
         if (!args.length)
             return unit(fn.apply(null, bound));
         return bind(args[0], function(a) {
             return bindr([x].concat(bound), args.slice(1));
         });
    })([], args);
}

If you want to use a more object-orientated pattern, then you could map the single combinations to argument tuples which you can apply in the end:

List([1]).nest(List([2])).nest(List([3])).do(function(x,y,z){ x+y+z })
// where
Monad.prototype.map = function(fn) {
    var unit = this.constructor; // ???
    return this.bind(function(x)
        return unit(fn(x));
    });
};
Monad.prototype.nest = function(m2) {
    return this.map(function(x) {
        return m2.map(function(y)
            return [x, y]; // tuple
        });
    });
});
Monad.prototype.do = function(fn, n) {
    function flatten(n, t) {
        return n<=1 ? [t] : flatten(n-1, t[0]).concat([t[1]]);
    }
    return this.map(function(ts) {
        return fn.apply(null, flatten(n || fn.length, ts));
    });
};

Is Crockford's implementation of monad correct?

Maybe. He does implement the idendity monad, but the code looks as if he wants to extend it to other monads by overwriting the bind method, which might not work easily for all monads.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375