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

This is a good post.

I know the point is broader than monads specifically, but I have to admit I think “explaining monads” have probably done more harm to understanding monads than anything to do with monads themselves. [Proceeds to commit the same error discussed in the post.]

In my experience, the best way to explain monads to someone who isn’t approaching them from a theoretical perspective is to relate them to abstractions they already understand—whether they’re actually monads or close enough if you squint.

I got there by relating the concept to jQuery. A lot of JS devs get there by relating it to Promises. (Aside: I had a minor revelatory moment when I realized the old Node-style `(error, result) => {…}` callbacks were essentially a Maybe/Option type in different syntax.)

It’s not a far reach, for learners with that familiarity, to go along for the next step where you can apply the same semantics to anything in a similarly wrapped structure.

That’s not “think of it as a burrito”, unless that example particularly resonates with the audience. It’s: once you have the aha moment, your teaching effectiveness depends on being able to recognize the pattern in your audience’s domain.

Which I guess is restating the post’s point, but I think it’s good to demystify monads in particular.




Personally, I think the best way to explain them isn’t to explain what they are, but to explain what they are useful for. You have map that maps (a -> b) functions, and you need another map-like for (a -> m b) functions. 99% of the time, the obvious way to implement that for your structure is the way you’d implement a monad on that structure (plus the ‘default’ a -> m a). That’s why they’re useful. Everything else is burritos.


If I am reading you correctly, all you need to get what you describe is the a -> m a function. Once you have that, it is trivial to go from (a -> b) to (a -> m b) as below:

  f :: a -> b
  return :: a -> m a

  g :: a -> m b
  g x = return ( f x )
The critical piece of monads is the ability to turn a generic (a -> m b) function into an (m a -> m b) function.

Essentially, if you have a two argument function whose output is your monadic structure, you can turn it into a function where both arguements are your monadic structure. This is useful because it enable compostability.

Since I'm pretty sure the above would confuse people who are not already familiar with monads, consider nullable types:

  f :: a -> Nullable b
  g :: b -> Nullable c
Suppose we want to compose these into a new function, h (switching to a more C like notation:

  h :: a -> Nullable c
  h(x){
     maybeY = f(x)
     if(maybeY == Null) return Null;
     y=maybeY.get();
     return g(y);
  }
With monads, if you let g' be the "modified" version of g that we discussed above, this just becomes:

  h(x) = g'(f(x))
In Haskell, this ' function is called =<<, and has its precedence set such that the above would be written as

  h x = g =<< f x
Although you often see it written the other way:

  h x = f x >>= g
Or with some nice syntactic sugar:

  h x = do
    y <- f x
    g y
In this last case, you can notice it reads exactly like the C-like code, except there are no explicit null checks.

Another common example is lists. You have functions that take a value and return a list of values:

  f :: a -> [b]
  g :: b -> [c]
You want to apply g to every element of f and get a 1 dimensional list containing all of the results. This is commonly known as flatMap.

  h :: a -> [c]
  h(x) = f(x).flatMap(g)
This looks simple, because flatMap is already the monadic bind operator. We just gave it a different name.

    h(x) = f(x) =>> g


> If I am reading you correctly,

I appreciate the effort at correctness, but you misread. The key bit was:

> You have map that maps (a -> b) functions, and you need another map-like for (a -> m b) functions.

I'm trying to write briefly, but I hope readers would interpret "map" and "map-like" as something from `m a` to `m b`. Or as you said:

> The critical piece of monads is the ability to turn a generic (a -> m b) function into an (m a -> m b) function.

We're saying the same thing. It's about using (a -> m b) functions in a map-like way.


What my intuition is lacking is the difference between an applicative and a monad.

Relatedly, what does one need to add (with concrete examples) to a functor (or an applicative??) to turn it into a monad.


My mental model is that applicatives are the multivariate analog of functors.

To invent some terms because I don’t know the real ones, call the types `Maybe Int`, `List Bool`, and the rest of the `m a` types “boxed,” compared to types like Int and Bool which we’ll call “unboxed.”

Functors apply single variable functions that apply purely in the unboxed domain. Take a `List Int` apply an `Int -> Float` function and you get a `List Float`. But you can’t use functors to take a `List Int` and a `List Float` to get a `List Bool` with an `Int -> Float -> Bool` function.

That’s where applicatives come in. Applicatives still operate in terms of applying functions that live in the unboxed domain. `Int -> Float` or `Int -> Float -> Bool` and that kind of thing. But if you had a `List Int` and something boxy like `Int -> List Float`, you have no way to get a `List Float` out the other end.

That’s where monads come in. These can operate on functions that don’t only live in the unboxed domain. You can take your `List Int` and your `Int -> List Float` and get a `List Float` out the other end.

As far as what you need to add for something to be a monad, I think there are cleaner answers that are logically equivalent, but what comes to mind is “flattening” (again for lack of remembering the real terms). Imagine you use the functor or applicative machinery on your boxy functions (that take in unboxed stuff and return boxed stuff). For example, using a `List Int`’s map on a `Int -> List Float`. You end up with `List (List Float)`. To build a monad’s `bind` out of that, you need another function to follow up: `List (List Float) -> List Float`. Aka, flatten that list of lists into a single list.


See I felt like the post was arguing for the opposite, saying approximate comparisons like a burrito or a promise were harmful to the learning process. I disagree with that and agree with you


At least for my understanding of the post, it’s not saying the analogy itself is bad. It’s saying the analogy you find clarifying is useful to you because it’s familiar and helps you relate the new idea. But that as a teaching/explaining tool it’s not necessarily useful because your student/audience won’t necessarily have the same familiarity or way of thinking about it.

In other words, it’s not analogies that harm sharing abstract concepts, it’s being presumptuous about what the other person thinks or knows in the concrete.


I actually thought that “burritos” from the article was a reference to “jQuery is a monad” type articles.


Exactly. Yeah, jQuery was that aha moment for me. Promises for others. Literal actual burritos for others still. Learning it out on your own, these analogies make perfect sense once they do. Teaching it, you’re not doing anyone any favors by explaining your particular “burrito”, you’re just obscuring it more.




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

Search: