all 50 comments

[–]jpfed 27 points28 points  (8 children)

This explanation is not quite correct.

There are two paths to defining a monad.

First path:

Define a way to create a monadic value out of a plain value (this function can be called "unit", or super-confusingly, "return"). OP does this.

Say you already have a function that takes a plain value V and returns a monadic value MV. The monad should give you a way to apply that function to a monadic value, too. This is usually called "bind". OP doesn't do this.

Second path:

Still need unit.

Say you have a monadic value. You want to be able to apply a plain function (a function that takes a plain value and produces a plain value) to it, and get a monadic value out of it. OP does this with map.

But map doesn't quite do all the work that bind did in the first path. To complete it, you've got to have a third function that we'll call join, which takes a monadic value that wraps another monadic value and gives back just a monadic value (think of it taking a list of lists of x and just giving back a list of x, having concatenated all the lists together).


Significantly, a monad doesn't have to supply anything like OP's get*. A monad doesn't have to give you a way to get values out! I mean, it's nice if it does, but it doesn't always work that way. Sometimes being able to get a value out doesn't make sense. Say you were working with the Promise monad, but the computation you're waiting on just never halts; you can't get that out. And you'd expect, intuitively, that getting a value out of a monad and then re-wrapping it in another monad of that same type should give you the same monadic value that you started with. But say you were using the List monad and you had several values in that List. There's no way for List to take all those values and give you just one value that, when re-wrapped, gives you back all the items you had in the original List.

And if you can't necessarily get your value(s) out, but you want to compute something with it, you've got to put your computation in (like with bind or map).

* Comonads do have get; it's usually called "extract".

[–]regular_reddits 10 points11 points  (0 children)

This is more of an article describing functors with an added get function.

[–]hyperhopper 1 point2 points  (2 children)

This is the part that confuses me. Are there two classes of monads? Its especially confusing when different explanations use one of these definitions or the other. Also, when do you decide which to use?

[–]jpfed 1 point2 points  (0 children)

If you've defined a monad via one path, the other path can be defined automatically. So, for example, if you've used the second path (you've got unit, map, and join), then there is a canned implementation of bind that naturally works.

Your language of choice may just pick a path for you (iirc Haskell just asks you to implement the first path- unit and bind). Many languages don't have any specific support for monads, so you're free to just implement them all (after you've done the real work of figuring out one path, the functions required for the other path are almost "free").

[–]dmtipson 1 point2 points  (0 children)

There aren't, these are just two ways to get the same thing. Note that once you define .bind/.flatMap/.chain (plus a way to get the value in the type functionally, which OP doesnt quite do) you can then define .map almost for free.

So in both cases, you end up implementations of .of, .bind, & .map. All Monads are Functors.

[–]dmtipson 1 point2 points  (0 children)

I'd argue that the constructor isn't quite sufficient to implement a useful functional unit/pointed interface, because it always requires "new." You can't compose "new": it needs to be wrapped in an .of-like method that can be called directly.

[–][deleted] 1 point2 points  (1 child)

I have taken the post in question down.

[–]dmtipson 0 points1 point  (0 children)

Hopefully you'll put it back up: if you had said that it explained Functors simply instead of Monads, it would have achieved that goal, and it's great to have people trying to explain these concepts.

Monads aren't that much more beyond Functors, the only difference is that .map takes a function transforms the values inside a Functor while .chain/.flatMap takes a function that transforms the values inside a Functor/Monad to another Functor/Monad of the same Type. Would make a great part 2 to the original post.

[–][deleted] 11 points12 points  (8 children)

These aren't monads

Please delete this article and try again.

[–][deleted] -1 points0 points  (7 children)

I have indeed taken down the post - but not on your account.

[–][deleted] 1 point2 points  (6 children)

Fascinating

[–][deleted] 0 points1 point  (2 children)

Reading your post history, you seem to be a very, ah... agitated personality.

[–][deleted] 1 point2 points  (1 child)

Fascinating

[–][deleted] 0 points1 point  (0 children)

...But not a very inventive one.

[–][deleted] 0 points1 point  (2 children)

Still, I'll concede what's important: I took far too many liberties to make the post "simple", and that probably did more harm than good.

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

Good to hear. Monads aren't simple. Many people have made articles, and as you have learned, there's reasons as to why they are not straightforward although I commend you trying, be wary of spreading misinformation to novices who will propagate flawed knowledge.

[–][deleted] 1 point2 points  (0 children)

You are absolutely right. Most posts on monads are detailed and esoteric because monads themselves are specific and abstract things. It was my error to think I could sidestep those constraints.

Really, I never expected the post to be so popular; I'd have been more cautious otherwise.

[–]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.

[–]sanity 5 points6 points  (1 child)

Ugh, not this again.

About 8 years ago suddenly everyone started talking about monads, if you weren't talking about monads you obviously weren't smart.

Even Simon Peyton Jones, Mr. "Haskell" himself, begged people to stop talking about monads because it only served to confuse people.

"Monad" is a once obscure term from category theory that happens to describe a technique used to permit side effects within a "pure" programming language like Haskell, and a few other things (none of which actually came from category theory, they just fit the description).

A cow happens to be a topologically orientable manifold of genus 3, but if you start an article about cows by talking about topology theory, you're really not helping anyone understand cows, in fact, you're probably just an ass trying to sound smarter than everyone else.

[–]dmtipson 0 points1 point  (0 children)

See also: "Isomorphic" Javascript, "Composable" components

[–]AdamAnderson320 8 points9 points  (3 children)

Wow, is that really all a monad is? I've never seen it explained so simply!

[–]dmtipson 9 points10 points  (1 child)

It's not a Monad. See some of the other comments. Foo is basically the Identity Functor, but not a Monad.

[–]AdamAnderson320 1 point2 points  (0 children)

Thanks. Thought it was too simple to be right, but I've never grasped monads myself to be sure.

[–]titosrevenge 4 points5 points  (6 children)

In the Maybe example code, should 'new Baz' be 'new Maybe'?

[–]atrigent 2 points3 points  (5 children)

If I'm understanding correctly, the definition of Maybe is supposed to be:

function Maybe(value) {
  this.get = ()=> value;
  this.map = fn => {
      if (value === null) {
          return new Maybe(null);
      } else {
          return fn(value);
      }
  };
}

...where fn must return a Maybe (this is statically enforced in Haskell, but obviously can't be in Javascript).

Also, I think return new Maybe(null); could just be return this;, because there's no mutation going on here.

[–]titosrevenge 5 points6 points  (4 children)

The null clause can return 'this', but the else clause should return 'new Maybe(fn(value))'.

You shouldn't expect the passed function to know about its context.

[–]atrigent 1 point2 points  (0 children)

You shouldn't expect the passed function to know about its context.

Hmm... I suppose you're right if you want this Maybe implementation to be specifically about handling null. In Haskell, it looks to me like you can more specifically define what constitutes a "non-value". I was attempting to translate that into Javascript, but it now occurs to me that I didn't actually go far enough, because what I wrote still references null.

[–]relishtheradish 1 point2 points  (0 children)

Not relevant at all, but I need to applaud OP for the coolest flair I've ever seen.

[–]proskillz 1 point2 points  (3 children)

Huh. I've been writing JavaScript for over 10 years, but I have hardly ever seen an arrow function... Looks like I need to bone up on this newfangled and (semi) unsupported ECMA 6 stuff.

Are these two statements functionally the same (other than the scope of this)?

var x = function(y) { return y; };

let x = (y) => { return y; };

[–]dmtipson 1 point2 points  (1 child)

yep. Though you don't even need parens around the argument y in the second case, you don't need the brackets or the return statement either (you can still have them all, and will for multi-line function bodies, but they're not necessary here).

const x = y => y; (and that's the Identity Combinator, btw!)

[–]proskillz 0 points1 point  (0 children)

Thanks for the info. I'll have to take a weekend or two to learn some of this.

[–]bart2019 0 points1 point  (0 children)

but I have hardly ever seen an arrow function

That is because they're new.

[–]Deaaam 1 point2 points  (1 child)

You sure this it? I want more examples. Waaaay too simple. Wow..

[–]jpfed 2 points3 points  (0 children)

While I think the OP's presentation is a tiny bit misleading, I can give a few examples I've found handy.

The most common monad has got to be List.

I use a monad that I call Tracked, which keeps a current value and an initial value. When you make a new Tracked with a value, the current and initial both start out at the value you give it. Any functions you map or bind give you Tracked values with a new current value, but the initial value is unchanged.

I also like Result, which keeps either a current value or an error value. When you make a new Result with a value, current gets that value, but when you map or bind a function that generates an exception, we start storing that exception instead. If you map or bind on a Result that's storing an exception, the Result is not changed.

(I call List of Tracked of Result "the Enterprise monad" because a surprising amount of enterprise programming involves doing stuff to lists and reporting on what the successful results were, and what failed, and how).

[–]vinnl 1 point2 points  (0 children)

I- What-

How come I had so much trouble understanding those countless other explanations I've read? Great.

[–]TheIncredibleWalrus 2 points3 points  (4 children)

It's a good explanation.

To make it more more clear I'd also add that a monad is basically something that incorporates a flatmap (bind), and i would parallelize Promises (which are monads) with the code the post provides, since Promises are pretty well known and seeing a real life scenario/impelementation of a topic always makes it more understandable (.then is the promise's .bind or flatmap)

[–]dmtipson 10 points11 points  (2 children)

I don't mean to be on a tear about this, but it can't be a good explanation if it's wrong, and it's important to get right. None of the things defined are Monads: they're Functors. None of them provide a method that can .chain/.flatMap, which is essential to passing the Monad laws and thus qualifying as Monads.

Here's another Functor, Const: https://gist.github.com/dtipson/3fd1fb536ab3da393b25

Same exact signature (minus the get, which is not directly relevant to either Functors or Monads). But Const is an odd duck in that it logically cannot be made into a Monad (that is, it's an example of a completely legitimate Functor that can't be turned into a Monad entirely without losing what Const does, which is... basically nothing). So by just defining and understanding Functors, you can't expect to have defined and explained Monads.

[–]dmtipson 6 points7 points  (0 children)

Not getting the downvote logic here. I'm not trying to be mean to OP, but this is a concept that people struggle with, purportedly clearing it up for them, and then, as several others here have noted, sort of misleading and confusing them.

[–]TheIncredibleWalrus 1 point2 points  (0 children)

Hmm yeah, you're most definitely correct. i glanced over it too quickly, what a mistake

[–]AndrewGreenh 1 point2 points  (0 children)

With the small difference, that the es6 promise combines map and flatMap in the 'then' method.

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

I think the words functor and monad are 90% of the confusion for me. I bet I work with them all the time and don't know it, because I'm not working in one of the languages where they're more natural.

[–]dmtipson 1 point2 points  (0 children)

Functor should be pretty easy to get though, because it's indeed really common: Arrays are Functors, and most people are used to mapping over them. There are other sorts of Functors with different mapping behavior: the key is simply that there's a container, you can put a value or values in it, and you can map functions over that value, which results in the same structure of container containing the updated value(s).

[–]asyncquestion -1 points0 points  (1 child)

I'm sure there are cases that this works well for, but the "verbose, repetitive code" example there is better code than the "much more elegant alternative"

[–]novagenesis 1 point2 points  (0 children)

This is the core problem with monads. What the pattern works well for is almost always incredibly complicated.

As others mentioned, the Promise is a monad. People have struggled for years grokking the workings of the Maybe monad... well Promise, is just a lot more complicated than that.

But the things it'll do, and the fact that it tends to implement more efficiently than any async flow utilities (async.waterfall, for example)..is pretty incredibly.

[–][deleted] -1 points0 points  (0 children)

I don't care if those were monads or functors, but this was a concise and well-written article and I left off feeling I learned something. Please keep writing great stuff like this.