Hacker News new | ask | show | jobs
by jerf 47 days ago
You may appreciate my own contribution, https://www.jerf.org/iri/post/2958/ , which includes an entire section titled "If They're So Wonderful Why Aren't They In My Favorite Language?", a section explaining why IO is not a good lens to understand monads and why "monads" don't really have anything to do with "making IO possible" (very common misconception), as well as what I believe to be one of the more practical applications of monads as a way of generating an audit log of how a particular value came to be what it is without. That example specifically arose from one of the rare instances I used the monad pattern in my own real code. Though I still didn't abstract out the monad interface, because if you only have one, that does you no good. The entire point of an interface is to have multiple implementations. It just happens to be a data type that could have implemented the monad interface, if there had been any use for such a thing in my code, which there wasn't.
3 comments

I enjoyed your article, thanks for sharing.

As I understand it, one thing the tutorial didn't go into, which I think is an important subtlety, is that it's not enough to have an implementation of "bind" to have a monad interface. You also need an implementation of "return : a -> m a" (i.e. a way of making sources of 'a's when given an 'a'), AND a proof that these implementations together satisfy the monad laws (i.e. that they "play nicely" together).

Without all three components, you can have something that "looks like" a monad, in that it has definitions for "bind" and "return", but isn't actually one, because those particular definitions don't also satisfy the monad laws.

Per the very last section, I chose to elide those. That's mostly because few languages worry about "laws" anyhow, and the lawfulness is less consequential in a non-lazy language because even if you nominally screw up the lawfulness, the code will still reliably do whatever it does. While I suspect we could find a pretty solid plurality of HN readers to be at least somewhat appalled at the idea, I think the generally programming world is not generally worried about it.

Plus it's rather like giving out criteria for how the frosting on the cake will be judged when most of the contestants are submitting piles of slightly dampened raw flour with an egg cracked over it and being offended when you won't agree that's a "cake".

Ah yep - I missed that mention of "lawfulness" in the last section. I guess the minor gripe I have is that that really isn't anything to do with Haskell: it's that you only have a monadic interface when the laws are satisfied (and Haskell itself doesn't, and can't, enforce the laws: you have to check them / others using your interface have to trust that you've checked them.)

I don't quite follow why you're making a distinction for non-lazy languages?

If you want to actually use any generic monad combinators with your monad interface, and expect it to behave sensibly, then the laws had better be satisfied!

But yeah... Nice article, and I really liked your "Noun / Adjective" distinction.

My understanding, which may be incorrect, is that the major reason that lawfulness matters in Haskell is the laziness makes it so that unlawful monads won't just do "something that violates the laws", an abstract, mathematical consideration that maybe you care about, maybe you don't, but that the combination of the laziness and the aggressively optimizing compiler means that result will be very unpredictable, and slight and seemingly isomorphic source changes can result in unpredictable results.

In Python, if I write an iterator on something pretending to be a list, and when it sees strings it doesn't just return an uppercased string but actually modifies the contents of the list to be uppercased, that's stupid, but at least since it's a strict language that isn't interleaved with IO and all the other stuff flying around in Haskell it will be consistently stupid. It isn't going to blow up or behave differently if I accidentally flip an "a + b" into a "b + a" somewhere.

It's bad, but Haskell has a whole different level of bad if you screw with it and don't play within the sandbox.

There is a definite "I'm being more pragmatic here than the average Haskell programmer" effect going on here. I... how to put this... "won't blink" is too strong, but... if I need to violate a law, if I need to write something like the stupid iterator above, I am in fact willing to. I have the decency to feel bad about it, and there will be extensive and probably bitingly sarcastic comments attached to it, but I'll do it. (Generally only when I don't control one end of the source code, though. If I have full control I never do anything that stupid.) But in Haskell it's a particularly bad idea, mostly because of the laziness and its interaction with other things.

And, heh, in a world where the struggle to explain what monads even are to people, monad combinators aren't even on my horizon.

> If They're So Wonderful Why Aren't They In My Favorite Language?

Aren't they now though? Like option is everywhere lately

Supporting "Option" is not "having monad". An Option data type can implement a Monad interface, but you can have an Option data type with no particular monad support in your language, or you can have an Option data type that implements something like "bind" or "join" but there's no interface that it conforms to.

If that sounds like gibberish it's because you don't have the right definitions loaded into your head. You can read the article I linked to fix that.

In this case note that what you are calling "Option" is called "Maybe" in Haskell and also in that article. There is an entire subsection explaining why using Maybe/Option as a lens to understand "monad" is a bad idea because by monad standards, it's degenerate, and degenerate instances of an interface make for bad examples. Just as if you're going to explain "iterator" to someone, starting out with "the iterator that returns nothing" isn't really a good idea, because it's not good to try to explain a concept with something that right out of the gate in some sense denies everything about that concept.

It's a common mistake. There's also some people who think that by adding flatmap to their list/array data type they've "implemented monads". No, they've just implemented flatmap on their list/array; they don't "support monads" by doing that. There are plenty of monad implementations that can't be understand as "flatmap", such as STM. ("flatmap" completely fails to capture the idea that a monad implementation may carry around additional data not visible from the level you're using the implementation on. That's one of the main reasons my example is structured the way it is in the article.) "flatmap" isn't "monad" in exactly the same way that "walk the next item in the array" isn't "iterator", or even more simply, "red" isn't the same as "color". Flatmap is an implementation of monad, walk the next item in the array is an implementation of iterator, red is an implementation of color.

Very few languages let you write a function that works for both Option and for other not particularly related monadic types (e.g. Future), while being fully typesafe, which is what I'd call "having monads".
Many languages have monads “by accident”, e.g JavaScript by way of Array.flatMap() - but the fact this type happen to satisfy the monad rules is not particularly useful.
I read this years ago and I think it's the best one I've read. Thanks for writing it!