Hacker News new | past | comments | ask | show | jobs | submit login

After I understood monads, I didn't write a short blog post about how simple they are. In fact, I took the opposite approach and am writing a ~500 page book about them :).

In seriousness, there's a lot that goes into appreciating a concept like a monad, especially if you want to combine the FP understanding with a rigorous mathematical understanding (monoid in a category of endofunctors approach). My new book is coming out next year with No Starch Press, and my goal is to give programmers a serious but doable introduction to abstract algebra, in the case that they care about this kind of stuff. Ping me if you want to read some early chapters and give feedback! orlandpm@gmail.com




> In seriousness, there's a lot that goes into appreciating a concept like a monad, especially if you want to combine the FP understanding with a rigorous mathematical understanding

Unfortunately, I think this is what tends to scare some programmers away from Haskell. I think it’s worth emphasising: an understanding of the mathematical underpinnings of monads is completely irrelevant to using them in an actual program! I know very little category theory, yet I am absolutely fine with structuring a program around monads. It’s interesting, of course, but far from necessary.


> It’s interesting, of course, but far from necessary.

I think this right here is where a lot of people go wrong with understanding them. Really, the best way to get them is to work directly with them. The mathematics is orders of magnitudes more abstract than the actual practical aspects of using monadic functions.


Completely agreed. The only way to learn about monads is to (a) see them being used in examples and (b) use them yourself — exactly like every other concept in programming. Just seeing an abstract explanation is not nearly enough.


Worked on a Haskell team as a test engineer for 14 months and struggled like hell to understand monads (one of the researchers there came up with using the damn things, but I couldn’t understand it for the life of me). Wasn’t until I stopped looking at it through their academic lenses and had to simply deal with the repetitive errors of several chained functions that I understood it.


Yeah, the interesting thing is that lots of people vaguely know when they have to use flatMap instead of map and still say things like “monads are too hard”.

You can go pretty far by “tricking” people into writing monadic code that works correctly without ever going through the bewildering problem of justifying monads in the abstract.


Yes, and I think Haskell monads are somewhat of an outlier in the way they're usually tackled. It's almost as interesting to learn exactly why Javascript promises are not monads, but you won't find any tutorials about Javascript promises that start this way, you just learn how to use promises.


Why are JS Promises not monads? Sincere question.


Monads are a particular pattern for function chaining. In OO terminology it is more like an interface than an object, so I would prefer to say that an object support a monad pattern rather than an object is a monad.

JS promises also supports chaining, but not in the same way as the monad pattern.

The Array.flatMap() method implements the monad pattern. The signature (in pseudo-typescript notation) would be:

   Array<T>.flatMap(T => Array<T>): Array<T>
I.e the argument is a higher order function which return the same type, and the function also return the same type. This gives unique flexibility in chaining functions, because this:

   [1, 2, 3].flatMap(x => [x + 1]).flatMap(x => x < 4 ? [x] : [])
has the same result as this:

   [1, 2, 3].flatMap(x => [x + 1].flatMap(x => x < 4 ? [x] : []))
So the functions can be chained or nested, arbitrarily, without affecting the result.

The above is equivalent to:

   [1,2,3].map(x => x + 1).filter(x => x < 4)
   
Which also use method chaining, but not in the same flexible way - you cannot change the chaining to nesting and still get the same result. So not every form of method chaining conform to the monad pattern. (For better or worse - map/filter is clearly easier to read and write, just not as composable.)

Note that the definition of monad says nothing about what the semantics of the chaining method (flatMap() in this case) actually is. It only defines the rules for how they can be chained.

Generalized, the signature is:

   Foo<T>.bind(T => Foo<T>): Foo<T>
(The method can be called anything, as long as it support the pattern. Traditionally it is called "bind".)

As far as I know, promises does not support a similar interface.


Great comment.

I think you are hitting the issue with monads tutorials. Most tutorials focus (or at that's where the reader will linger) on the flatMap "interface" and the box-or-container-of-things while mentioning monads laws only in passing.

So the reader is left wondering what's the big deal with an IFlatMappable interface other than, yes, it is a common pattern seen in the wild.

But the real power are the laws and the transformations. I suspect that for most procedural (with a sprinkle of FP) code those laws probably don't give you much, and if really needed the programmer might just derive the the specific transformation by local reasoning. An initiate of the FP arts will have internalized the transformations and will structure their code to take advantage of them as much as possible.

I say this as a boring procedural programmer that most definitely has not internalized monad laws.

As an aside, is it possible to further generalize monads and allow flatMap to interoperate with any monad instead of the same monad of its input? For example here the internal flatmap function might return an optional instead:

   [1, 2, 3].flatMap(x => [x + 1].flatMap(x => x < 4 ? Just(x) : None))


Promise<Promise<T>> is collapsed to Promise<T> automatically, which violates monad laws.


What’s interesting is this seems to say the exact opposite of what the other direct response says. But I also don’t think this is exactly true.

    Promise.resolve(
      Promise.resolve('foo')
    )
At runtime is Promise<Promise<string>>. It only collapses when chained to then (or await’ed), just as in the sibling comment’s flatMap example.


I might be sending you an email…

I find that category theory often starts to feel like Inception math, where it’s very easy to lose track of what “level” one is operating on. The statement “category of endofunctors” is a good example; for someone with a very visual intuition like myself it feels like something that simply cannot be visualized, or is akin to visualizing a 5d hypercube.


> The statement “category of endofunctors” is a good example; for someone with a very visual intuition like myself it feels like something that simply cannot be visualized, or is akin to visualizing a 5d hypercube.

Follow this recipe and don't skip any steps:

1. Understand what a category is. Objects and arrows between the objects, composition of arrows, identity arrows.

2. Understand what a functor is. Associate each object of one category with a corresponding object in a second category. Do the same for arrows.

3. Understand that an endofunctor is just a functor where the first and second categories are actually the same category. These are the functors that show up in programming: Maybe/Option, List, etc. The objects of the category are types and the arrows between them are functions.

4. Understand what a natural transformation is. In programming terms, a natural transformation is like a generic (in the OOP sense) function. Example: length : List<T> -> Int is a common natural transformation that takes a list and returns its length, and it works for any type of list (i.e., for all T). This particular natural transformation goes from the List functor to the constant functor that maps every type to Int (see how T doesn't show up in the return type in this example).

5. If natural transformations go between functors, consider making a category where the objects are functors and the natural transformations are arrows. This is the "category of endofunctors" you're looking for. If you have trouble visualizing it, any category can be visualized as a directed graph: the nodes in the graph are endofunctors like List, Option/Maybe, etc. and the edges are natural transformations like "length".

Each step will require you to Google something, stare at its definition, and spend time with examples. The one mistake that everyone makes is thinking that they should understand the phrase without first learning the constituent words. There's nothing mysterious or clever or tricky going on. You just have to know how to break it down into an incremental curriculum (which I've done for you here).


I know what each of those things are. The problem is that there isn’t a tidy image (at least I haven’t come up with one) that combines all of it.

Yes, visualizing a category is easy: directed graph. Visualizing a functor is also not much of a stretch. But combining these two? Not sure what image is appropriate.


> The problem is that there isn’t a tidy image (at least I haven’t come up with one) that combines all of it.

I don't think you should expect there to be an image that shows all the levels of abstraction at once. The image should only show you the level of abstraction that you're currently concerned with: in this case, endofunctors and the natural transformations between them.

What you're asking for is akin to asking for an architecture diagram of a distributed system that somehow also shows you the circuits inside the CPUs.

The point of abstraction is to package up details into a box and forget about them, for the purpose of managing the complexity as you reason at a higher level.


I support your work on this -- I've always wanted to see a full-length book exploring monads, and will definitely read it when it comes out. (Unfortunately I don't have time to review chapters right now.)

One suggestion: you might also want to discuss arrows/bolts, which are a generalization of monads: see https://en.wikipedia.org/wiki/Arrow_(computer_science) The Fudgets library in Haskell is a good example of the value of this abstraction.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: