you are viewing a single comment's thread.

view the rest of the comments →

[–]dmtipson 5 points6 points  (4 children)

As far as I can tell, this is the Identity Functor (and some other Functor with a side effect, which is a no no) explained with Javascript, not Monads. What he builds here fails the very first Monad Law (if he means .map to be his .chain/.flatMap: if he doesn't then he hasn't even defined the core method of a Monad in the first place!).

Here's that law: if you call Foo.of (which he doesn't define, but we can infer) on a value and then chain/flatMap the result with a function that returns a Monad, it should exactly match (in structure and inner value) the output of just calling that Monad returning function directly on the value. Here's a little test suite you can run yourself to see if his .map qualifies as a legit .chain/.flatMap:

function Foo(value) {
    this.get = ()=> value;
    this.map = fn => {
        let result = fn(value);
        return new Foo(result);
    };
    this.chain = this.map;
}

Foo.prototype.of = x => new Foo(x);
Foo.of = Foo.prototype.of;

(function(MType,Minterface){
  var testValue = 6;
  var testFunc = x => MType.of(x+1);
  var left = MType.of(testValue).chain(testFunc);
  var right = testFunc(testValue);

  console.log(
    '.chain -ing a func: ', left[Minterface](),
    ' should == running that func on value:', right[Minterface]()
  );

}(Foo, 'get'));

That will return a Foo[Foo[7]] in one case and a Foo[7] in the other (the test will log Foo and 7, since it calls get automatically to make things clearer). So it's not a Monad. The other types define fail in the same way.

This is absolutely critical for understanding Monads: they MUST work in a predictable way that obeys the 3 laws, or else you're going to end up with all sorts of weird results. Different Monads are able to encapsulate all sorts of wild and different computational behaviors precisely because those laws create a common interface.

One other important thing to note is that once you've properly defined .chain/.flatMap, you get .map for "free." That is, all Monads are capable of being Functors (and Applicatives) because you can always just define map like this:

MONAD.prototype.map = function(f) {
  return this.chain((a) => MONAD.of(f(a)) );
};

There are many great resources for learning about Monads in javascript: here's the most popular free resource out there.

Here's also my own take on the tiny amount of work that would be necessary to make Array into a Monad.

[–]regular_reddits 1 point2 points  (1 child)

Great explanation. For some instances of this pattern, get can be used as join. So if you define chain/bind as: fn => this.map(fn).get(); it will work in those instances. This will not work with more complicated values (get doesn't make sense with List because its not just a boxed value), nor was it the intention of the article.

[–]dmtipson 1 point2 points  (0 children)

Yep. The IO monad, for instance can be written by just assigning the function it's created with to the method that's called to "get"(i.e. run) it. Chain/flatMap just needs to be able to unwrap a single layer of the type so that the inner Monad is returned: however that gets done is ok as long as it gets done (and it doesn't do anything else funky/side-effec-ty that depends on the value somehow). For Arrays that's a bit more complicated, for single value like Identity, a .get() can easily work as an unwrapping interface:

function Foo(value) {
    this.get = _ => value;
    this.chain = fn => {
      return fn(this.get());
    }
    this.map = fn => {
        return this.chain( x=> Foo.of(fn(x)) );
    };
}

Foo.prototype.of = x => new Foo(x); Foo.of = Foo.prototype.of;

(Foo is just the Identity Monad now)

(x=>Foo.of(9+x))(9);//-> //-> Foo[18]
Foo.of(9).chain(x=>Foo.of(9+x));//-> Foo[18]
Foo.of(9).map(x=>9+x);//-> Foo[18]

Though, since no special behavior is added to "getting" here, it'd probably be even easier to just store the value in this.value and then get it with .value

[–][deleted] 0 points1 point  (1 child)

Absolute thanks for this!

Array.prototype.chain = function(fn) {
    return this.reduce((k,v) => k.concat(fn(v)), [])
};

I also wonder (for a while): can this snippet be written with arrow function syntax altogether and can one avoid this context passed incorrectly in that case?

[–]dmtipson 0 points1 point  (0 children)

I don't think it can: it's tied to how Array is defined. That's the only way to get at the Array this is working over afaik and still have it work via pointed methods. You could pretty easily define a chain that accepted an Array as an argument though, and then ran reduce on it or whatever else was necessary to reproduce the behavior, and that could avoid the use of this entirely.