Hacker News new | ask | show | jobs
by darkkindness 2389 days ago
I always see monad tutorials like this conflate (1) "understanding using particular monads" with (2) "understanding using monads in general", which are completely different things. Luckily it's easy to not confuse those two with (3) "understanding monads abstractly" which tends to break out the category theory, but some tutorials like jumping on that too.

To demonstrate the differences, suppose now that you're deep diving into a codebase which depends on a Async monad. Your eyes light up and you go aha(!) that means async programming, I've done that before! The point is, you saw "Async", you know it means "make some task's outcome depend on a previous task", which is the behavior of bind for that particular monad. Without (1) the word "monad" is really unnecessary. Why not just refer to bind as some arbitrary async-API function? In summary, you don't need (1), (2), or (3) for this! Monad is just a word.

Okay, when do we need (1)? Let's say you've changed companies, and now you're plugged into another codebase and oh(!) there's that word "monad" again, but this time it's the "Future" monad. Another whole API to learn... but soon you realize that it's again the same old "make some task's outcome depend on a previous task". Except they just call it "Future", and instead of "bind", they say "flatMap" which is the same thing! And you bump into it again and again with different names -- Deferred, Aff, Task -- but in the end it's the same behavior and API. How upsetting that they don't call them all Async and be done with it! But if you have (1) you'll know they are just the same monad, given different names. That's (1): familiarity with the usage of a particular monad. Later on you might collect understanding of the Option/Maybe/Nullable monad, or the Result/Either/Error monad, or the List/Stream/Nondeterministic monad, so now names don't bother you because you know CONCEPTS, but all of this is still (1) if each monad is being treated as an independent API to grok.

That's where (2) comes in. Suppose now that you're deep diving into a codebase which depends on the "Anisotrope" monad. You've never heard of this before[0], but you know monads in general i.e. (2), so you know you can make functions whose output (an Ansiotrope object) depends on some property of a Ansiotrope object, and that all the details about this dependency can be found in the definition of bind for this particular monad. In other words you can use knowledge of (2) to quickly adapt to new monads without having to regard them as a completely new concept. It lets you answer questions like "what's this Probability monad supposed to do? what about this Forkable monad?" in a general way. Conversely, you can now start writing monads of your own by thinking about what bind could mean for your new "Transmogrifying" monad.

Is this useful? Imagine learning all OOP patterns knowing that every pattern is the same concept except for this one thing: bind. That means it's relatively trivial to learn these patterns and make new ones. Except they're not OOP patterns, it's monads, and no, you can't generalize OOP patterns the same way.

Anyways, this is also where "monad is a box"-esque metaphors fall short. It's rather descriptive of particular monads, but doesn't generalize well when you start reaching for many others: Reader(environment-passing), List(nondeterministic computations), Probability(probabilistic programming), or fun ones like parser monads, Amb(backtracking), Tardis(state-access-timeline-specifying)[1].

[0] I hope not, since I made it up. If you have (3) you're probably deriving a working definition already. Have fun! [1] http://hackage.haskell.org/package/tardis-0.4.1.0/docs/Contr...

1 comments

I really like the distinctions you are drawing. And I think that it's better for people to walk through this in (1), (2), (3) order rather than jumping to (2) or (3) before they know (1) on at least one monad.

> Imagine learning all OOP patterns knowing that every pattern is the same concept except for this one thing: bind. That means it's relatively trivial to learn these patterns and make new ones. Except they're not OOP patterns, it's monads, and no, you can't generalize OOP patterns the same way.

From the OOP perspective, though, monad is a pattern where you only have one behavior (bind) that you can customize, and you have to do everything through that one behavior. That looks like a straightjacket. Sure, it composes well, but... why limit yourself like that?

Thanks!

> From the OOP perspective, though, monad is a pattern where you only have one behavior (bind) that you can customize, and you have to do everything through that one behavior. That looks like a straightjacket. Sure, it composes well, but... why limit yourself like that?

That's an excellent question. Thinking of monad as a pattern itself, with bind as its API, made me realize that OOP patterns (as a categorical concept) aren't the best analogy for the different behaviors monads encode, but rather monad is just one of the patterns. Makes sense -- there are obviously behaviors not generalized by monads but are in the realm of OOP patterns. For instance, the visitor pattern is covered by functors.

(At the time of writing I was thinking about how bind can relate seemingly disparate behaviors: error handling, async, state, etc. What other useful structures seem disparate? OOP patterns! And so I fell into the black pits of "Monads are just like <thing>" myself, where thing = OOP patterns.)

As for the question: One could call it a straightjacket but one could also call it "the law". Limiting yourself is exactly how Straightjacketed Sam can ensure correctness properties of the program. Monads force Sam to work within the monad, so they can only write "a depend on b" in a specific and well-defined way. This is analogous to how singleton pattern would force Sam to work with this one instance of an object, or how the factory/smart constructor pattern forces ensures only certain (valid) objects can exist. If any of these restrictions are too limiting, there's no need to use the patterns, but you'll also lose the laws (guarantees)!

Lose the straightjacket, lose the guarantees. That makes sense. I had been thinking of it as just a straightjacket.

And, as you point out, there are some OO patterns that we can also provide useful guarantees and constraints.