Hacker News new | past | comments | ask | show | jobs | submit login
Abstraction, intuition, and the “monad tutorial fallacy” (2009) (byorgey.wordpress.com)
101 points by Tomte on Sept 30, 2021 | hide | past | favorite | 79 comments



> What I term the “monad tutorial fallacy,” then, consists in failing to recognize the critical role that struggling through fundamental details plays in the building of intuition. This, I suspect, is also one of the things that separates good teachers from poor ones.

I generally agree with this, the punchline of the article, but I’d say it separates the competent teachers from the incompetent ones. What makes a teacher great is the ability to probe a student’s current intuitions, and then chart a direct and efficient pedagogical course that starts from the right place.


Some languages have frameworks with monads, currying, higher order functions, and other stuff that only makes sense if you have immersed yourself in functional programming, geeked out on type theory, etc. Been there done that ... 25 years ago.

Since then, I've learned that other languages exist that are often dismissed as impure, not worthy, etc. that take the more useful functional programming concepts and implement them in a straightforward / pragmatic way that can be explained without using a lot of big words to solve real problems. Functional programming has gone mainstream about 10-20 years ago when javascript, Go, Rust and a few more recent other things took off. Most of the popular languages today borrow heavily from features that used to be associated with functional programming or have gradually acquired such features (e.g. Java, python).

Side effects and state are topics that you have to worry about when doing asynchronous stuff. Like most Frontend and modern server side developers would need to deal with. The most popular languages for this are not pure functional languages. But they do tend to have lambda functions, reactive frameworks, functions, etc. that can be passed around as values, async await, coroutines, etc. Maybe, Optional, Result and other/similar constructs. But the word monad is not something you'd find in their documentation even though of course they kind of are there lurking in the background.

My simplistic interpretation of monads is "dealing with side effects by using functions". Useful when doing IO, dealing with error responses, and a few other things that break the leaky abstraction of "everything is a function". Not that big of a deal generally unless you make it one.


> Functional programming has gone mainstream about 10-20 years ago when javascript, Go, Rust and a few more recent other things took off.

The problem with this is that Javascript, Go, and Rust are merely "multi-paradigm" (meaning imperative and procedural / object-oriented) languages, not functional programming languages. They may have some of the trappings of functional programming such as first-class closures, but they lack the mathematical functions (i.e. pure functions which deterministically map formal arguments to results with no side effects, as opposed to imperative procedures) which are the key concept of functional programming. You can't achieve functional programming by adding closures or coroutines or procedures-as-values to an imperative language. You can only get there by removing things like side effects which are incompatible with functions.

Of course a program which consists of nothing but mathematical functions isn't very useful, since it can't interact with its runtime environment (no I/O), so you end up with a split between an imperative outer shell (e.g. the Haskell runtime) and a functional program which computes a stream of actions from a corresponding stream of inputs. At that point the concept of monads becomes very helpful to ensure that your input and output (action) streams remain synchronized and that the type of each input matches the corresponding action. As a bonus, the monad concept can be generalized to work in areas other than I/O (exception handling, nondeterministic computation, and probabilistic modeling, to name a few), and you can design combinators (e.g. loops) which are useful in any monadic context, not just the I/O context for which most languages provide special syntax.


On this page I have heard so far: Monads are...

* just a way of defining a list

* very much not that

* dealing with side effects using functions

Apparently the article is spot on. The concept may be understood, but may not be communicated.

It's quite interesting seeing the phenomenon play out just as described.


Though I'm not certain I do understand monads, this is what I eventually made sense to me from various tutorials: A container datatype that abstracts control flow in a composable way.


One popular example of a monad is a type constructor that extends the given type - a “wrapper”, if you will, where a value can be wrapped in a trivial way (e.g. x -> {x} or x -> x?), and a double wrapping can be trivially unwrapped (“naturally transformed”) into a single one: {{xyz}} -> {xyz}. (Note that unwrapping the last remaining layer will not be always possible - e.g., while {x} -> x does indeed make sense, {x, y, z} -> … or x? -> x do/may not.)


> where a value can be wrapped in a trivial way (e.g. x -> {x} or x -> x?)

> and a double wrapping can be trivially unwrapped (“naturally transformed”) into a single one: {{xyz}} -> {xyz}

You've described the "pure" and "join" operations (in Haskell terms), which are two of the three requirements for a monad. The third and final requirement is that there must be a way to lift an arbitrary function on plain values to work on "wrapped" values: (x → y) → ({x} → {y}).


Yep agree. I actually understand Monads and have used them in Haskell but I still 100% agree with you. One of the problems is that understanding Monads is 100% needed in purely functional languages but not in non-pure functional languages. I have never used Monads outside of Haskell because it doesn’t make sense to use them in C++/Java/C#.


Interesting construct, the teacher as the personal guide or someone to help you get unstuck rather than a deliverer of knowledge.


https://en.wikipedia.org/wiki/Student-centered_learning ; hardly new, but extremely controversial because of the number of people who want education to be a predictable, standardised knowledge-stuffing process.


The trap is teaching (explicitly or inadvertently) a reductionist mental model that must be unlearned before true understanding is reached. That takes care.


When a thing has been created to solve a problem, it helps to first learn what the problem is before trying to understand the solution - and one of the best ways to understand the problem is to run into it and spend some time trying to work around it, with examples that are simple enough to understand but realistic enough that the student can see the issue matters.

If there are no examples that are simple enough for this, the student has not yet advanced to the point where they will understand the issue.


" simple enough to understand but realistic enough that the student can see the issue matters."

Exactly this.

My least favorite method of teaching is when they dive into a tutorial or example which has nothing to do with the actual problem.

E.g. when I was independently trying to learn how to code in high school (before my CS program in college), I was using some C++ book that started with a 'Hello World' program. I had no idea what the context for 'Hello World' was and why computer programs need to 'print' stuff. It was just a world of confusion. I remember working through it and not being satisfied at all. Why did I care that my terminal was telling me 'Hello World'?

On the other hand I had amazing CS professors in college who would introduce their course with the real-world problem its solving and the ideal end-state of mastering that area of coursework such as writing a programming language or creating optimal algorithms.


Nothing has been more educational to me than running into a problem, trying to solve it and then finding out there is an entire branch of mathematics devoted to coming up with ways around solving it.


One of the best examples of this I can recall came from an operating systems class at uni, 15 years ago. The professor introduced a problem where two threads are trying to modify the same state at the same time.

Then, instead of saying “there are these atomic locking primitives that we can use,” he asked the class for suggestions about how to make sure the two threads don’t stomp on each other’s updates. For about a half hour, someone would suggest a new way to place an if statement, and he would show how the “lock” would be defeated if contexts switched in such-and-such way.

Finally, when the whole class was thoroughly convinced that it wasn’t possible, he introduced atomic locking primitives.


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.


Sometimes you just need to grind in order to acquire a new skill...

Another good example is playing the piano by ear: if you look at YouTube tutorials, they kind of make sense, but if you try to apply those techniques in real life, you tend to fail miserably the first time you try it. I've been playing a bit on the piano everyday, and am now at a point where I can figure out the chords for easy songs in a couple of minutes. There was one video in particular that helped me big-time more recently, but if I had seen the video a month ago, I doubt that it would have been as helpful as it was recently. There might also be lingering some knowledge in my head from videos I watched previously but didn't grasp at the time, so I'll never know for sure.

These kind of skills are all about repetition and growing an intuition for the basics, so you can start thinking at a higher abstraction level...

(for the record: I'm at "campfire level skill": I might take another few decades before you can call me a good player, but it's good enough for having fun and the occasional gig with friends among friends a couple of times a year...)


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.


Of course monads are burritos! return represents the common operation of wrapping the filling into a burrito. bind the also totally common operation of unwrapping a burrito, re-cooking the filling and wrapping it back. The Maybe monad is that common feeling of wondering if you've been served a burrito with no filling. The List monad represents the endless potential of adding 5 more toppings to a 25-topping burrito. And the I/O monad is a discussion about burritos that involves not eating any, such as this one.


These days, all I say about monads is that they're a design pattern (albeit a rigorous one) for sequencing well-defined operations with controlled side effects over a container type. They speak for themselves more than tortured analogies involving alligators or tacos ever could. Design patterns are something every programmer can relate to, and no bizarre analogies are necessary to explain monads in terms of them.


Monads are a weird infohazard. They are very simple, but anomalously challenging to grasp, so once you grasp them and realize how simple they are, you feel compelled to write something explaining them, targeted at yourself five minutes before you figured them out.

This does work, in that if you read enough of these blog posts you can also figure it out, but it’s not pedagogically efficient.


Pointers are probably in the same category (ha). Notoriously difficult to grasp as a beginner, notoriously difficult to understand why they're difficult to grasp after you "get it", and rife with subpar analogies like "pointers are like the numbers on your house".


Just give me a cookbook that shows how to, .e.g., print out a haiku while numbering the lines.

We can work some toy examples and then trot out the abstruse pet-a-doggie or whatever.

Resist the urge to avoid practicality.


> print out a haiku while numbering the lines.

Little need for monads to do this:

    main = readFile "haiku.txt" >>= putStrLn . number
      where
        number = lines . zipWith addNumber [0..] . unlines
        addNumber n l = show n ++ " " ++ l
(Untested, but should work.)


> Little need for monads to do this:

What do you think >>= is? It is the piece unique to Monads.

So your “little need for monads” example shows the opposite.


If you’re willing to take input on stdin, you can rewrite it without >>=:

    main = interact number
      where
        number = lines . zipWith addNumber [0..] . unlines
        addNumber n l = show n ++ " " ++ l
My point is that the logic itself does not require the idea of monadic sequencing — it’s relevant only for the minimal amount of surrounding IO.



Sorry, nope. I didn’t bother proving correctness for such a small toy example. (Besides, I wouldn’t know how.)


Not concerned with whether the example as such is a poor use-case.

Just seeing the mechanics is the point.


Monad tutorials are hard to understand because nobody actually wants to tell you what a monad is, because that would defeat elitist thinking.

A monad is just a very concise definition of a List. In java terms it's as if you're defining the List interface in only 5 methods. It is of course a very restrictive definition, but this makes it easy to redefine listy/listlike things as Monads with minimal fuss, and it turns out that when you put on listy glasses, you find listiness everywhere, because it's a fundamental concept in computing. For example, a string is a list of characters; a map is a list of keys, a list of values, and a list of key-value pairs; a byte is a list of bits. Java programmers get excited about the new Optional<> class, which in the monad world is famously a list of 0 or 1 generic things.

Monads enable a sort of programming-language parlor trick where you can flatten a bunch of nested for-loops into a single "for-comprehension" (or do-comprehension, list- comprehension, whatever) that looks like a fancy for-each-loop but is actually desugared calls to the Monad methods. The semi-magical part is that because Monad methods are all callback-based (lambdas), this makes it easier to resolve the problem of "callback hell" where you discovered lambdas, got carried away, and then you have a nested mess that you really want to flatten out.

That's pretty much it. Monads are clever, but not remotely close to being some kind of window into ultimate truth of everything. Elitism==extinction, and so we don't get to have nice things.


> Monad tutorials are hard to understand because nobody actually wants to tell you what a monad is, because that would defeat elitist thinking.

Tutorials aren't for telling you what a thing is but for developing an intuition around why, in practical terms, it is. Telling you what a monad is is easy, brief, and fully handled by the typeclass definition and monad laws. But a tutorial which just presented them, while perfectly accurate at telling what a monad is, would be a crap tutorial.

> A monad is just a very concise definition of a List

Its not, because monads are much broader than lists, and the definition of a monad is less concise than that of a list. So its pretty much exactly not that.

That monads encapsulate something like your vague concept of “listiness” in some sense is usually part of tutorials, because its a useful fuzzy intuitive handle. But its not alone sufficient to develop enough understanding to use, any more than the definition and laws are, so tutorials need to, and do, present more.

They tend to suck because monads are a very abstract concept, and one which doesn't closely correspond to thing people tend to have a good concept of outside of people versed in category theory (I assume, not being one of those people I can't say for sure.) But the high level of abstraction is why they are useful, because they capture related parts of behavior of things that intuitively don’t seem very similar.


No Monads are not “just a very concise definition of a List”. I do agree that some developers love to obfuscate rather than clarify because it makes them seem smart. But not everybody. I agree with the article that you really have to try and implement your own Monads to understand them. Reading articles won’t help. However unless you are programming in a pure functional language you don’t need to learn/use Monads. So just ignore it if that’s the case.


Building understanding is like building muscle, it needs to be done against resistance and with a degree of struggle.

Building understanding, and then building intuition.

That one quick trick that gave some body builder/weight trainer the edge, without the accompanying resistance training is unlikely to work, but in their 'tutorial post' they emphasize the "burrito" part of their regime (the one unique tweak to their regime) - and neglect mentioning the hours of pumping iron.

Learning is integrating experiences into an internal model and with most domains of expertise this is more difficult than just learning rote facts.

I'm excited to see how a more general cultural experience with machine learning changes our ideas of learning from a facts-in-facts-out mentality.


It's weird how the author starts with "examples and concreteness are essential to the process of building up an understanding of abstract pure ideas", and then rejects the burrito example as an unhelpful leap when it comes to teaching.

To me that's contradictory. A concrete and familiar metaphor is just as good as a concrete example when it comes to building up the foundation upon which abstract ideas eventually form.


The fallacy is believing that a complete fantasy metaphor like burritos is as useful as a concrete example like "the [] type forms a monad with concatMap and singleton".

The latter actually tells you something. You can start from there, add a few more concrete examples, and start to understand what exactly monads abstract over.

The former is... Nothing. Nothing at all. It communicates exactly nothing. Or rather, it communicates so many hundreds or thousands of different potential metaphors that it doesn't serve to transfer meaning between two people.

And that's the point of the article. Monads are abstract at a level foreign to most programmers. When they first start to put the pieces together, they might enlist the aid of a metaphor, but the way they do so requires a lot of internal context. To others without all that context, the metaphor is useless.

It's easy to mistake metaphor for the understanding you've slowly built up. This article is a reminder that the understanding is the important part, not the metaphor.


The point is that burritos are not monads.

Metaphors are things you see in literature. A key feature that makes them attractive in literature is that they are subjective and can be read in multiple ways. That is the very thing that makes them a poor device to use in technical writing.

We do use analogies a fair amount in technical writing, but even those need a careful author to make sure they are made clearly.

The core idea of an abstraction is that it is a generalization of something you want to do a lot of. It makes a lot of sense to go over a bunch of examples and explain how they are all the same thing.


There's a difference between a concrete example and a familiar metaphor. I think the former is extremely important for learning. I think the latter usually leads instead to a false feeling of understanding that then leads to completely off base conclusions (my favorite example for this on HN is mathematical philosophy, e.g. Godel's incompleteness theorems).

Hence while concrete examples of monads are very important for learning the abstract pattern behind them, I agree with the article that abstract analogies usually do more harm than good.


>A concrete and familiar metaphor is just as good as a concrete example when it comes to building up the foundation upon which abstract ideas eventually form.

Right, but, "monads are burritos" is the lie that maybe what made monads click for you; there's no guarantee that that particular lie will work on me! Maybe for me, monads are schwarma.

And what's more, they won't _be_ schwarma for me until i've done enough head-banging and questioning and scribbling grumpily on whiteboards for the actual learning to happen. I'll _consolidate_ on a metaphor (burritos, schwarma), but how i got there is what did the trick.


I feel that there is an intentional device that the author came up with this abstract fallacy after reading a bunch of examples. However I've never read a tutorial on monads, but I totally understand the point of the article. It permeates all development, but to call it the "monad tutorial fallacy" means nothing to me since I have no experience with monad tutorials. =]


Well,

I noticed too that the author calls this the "monad fallacy but nothing about the argument is specific to monads. The main claim is just "people just have to work through hard problems, there aren't shorcuts".


Except programming is hard enough that we don't need abstractions that make it harder.

Some people say that they think something is wrong with object-oriented programming, but can any alternative that is so hard to explain be better?

There's a real problem in the way people communicate about programming. Lately I've been thinking of the phenomenon where people code something up in Youtube and quickly get lost in the weeds.

(There was that guy who was going to code up the Infocom Z Machine in Verilog and burn it to an FPGA who worked for seven hours and never got the program counter working.)

I've been thinking about what it takes to make a tutorial video that makes sense, isn't rambling, doesn't apologize for what it does wrong, etc.

I was writing up a manifesto for my side project and quit (writing the manifesto, not the side project) when I found myself explaining the things I was doing wrong. I decided I'd do all those things right and come back to the manifesto when I didn't have to apologize for anything.


> Some people say that they think something is wrong with object-oriented programming, but can any alternative that is so hard to explain be better?

Personally, I think that OOP has the opposite program. Abstractions such as classes, encapsulation, polymorphism etc. are to some extent easier to explain than abstractions such as monads, at least coming from an imperative background. But on the other hand, they’re much more difficult to apply correctly. I know I was greatly confused by how to use these OOP concepts until I looked into Smalltalk, which was the first time I had actually seen these concepts used effectively. By contrast, once you understand them, it is basically impossible to apply monads ‘incorrectly’. (I’m not even sure what that would mean.)

I suppose you could counter that this is just because OOP concepts are often explained poorly. But I’m not convinced that OOP concepts are any less difficult than monads to explain well. At most, I suspect it is easier to give someone the illusion of understanding OOP.


> There was that guy who was going to code up the Infocom Z Machine in Verilog and burn it to an FPGA who worked for seven hours and never got the program counter working.

Potentially interesting project, but I can see myself spending 7 hours planning that before starting, and I absolutely cannot imagine the mindset of anyone watching live coding. Even worse if it's genuinely live and you can't use the youtube lifesaver of 1.5x speed.

Anyone learning programming from Youtube faces the same problem as people learning medicine, politics or history from Youtube, just with less dangerous consequenses.


My take on this was The Monad Challenges (https://mightybyte.github.io/monad-challenges/), which attempt to provide a guided map through the grind of developing your own intuition about monads. Credit to the matasano crypto challenges for the inspiration.


And, ironically, this post inspired an actual "monads are like burritos" post that is pretty good.

See https://blog.plover.com/prog/burritos.html for why monads are like burritos.


What an excellent article. He is 100% right. And the sad truth is that most documentation/tutorials/teachings does it wrong.


Can someone just… for the love of god start writing _practical_ examples?

Ex: “I need to sequence these image processing operations in this manner while dispatching side effects at the specific steps. Let’s look at how we’d structure our code using monads to accomplish this.”

See that makes sense doesn’t it? :)

Stop with the mathematical BS, 99% of engineers won’t need to know that to solve a _business need_


You don’t need Monads unless you are programming in a pure functional language like Haskell/Lean/Idris/…


Non-functional languages use monads all the time. The difference between them and functional languages is that non-functional languages have ad-hoc syntax for each different monad (e.g., in JavaScript, Array.flatMap, optional chaining with ?., and the await operator each have their own syntaxes), but functional languages abstract out a common pattern and syntax for them all (e.g., Haskell's equivalent of all of those things can all use the >>= operator and the do-notation syntactic sugar).


My point is that you need to understand Monads to use Haskell/Lean/Idris/Coq for anything practical. Because those languages are pure. You don’t have to understand monads to use non-functional languages. In the same way that you don’t have to understand Category Theory to use Haskell. So when I say you don’t use monads in non-functional languages that is exactly what I mean. In the same way that you don’t use Turing Machines in any programming language even though it “is” a Turing Machine underneath.


It's a fallacy that one has to understand monads to be able to use them. I don't have to understand a car to be able to drive it. I have to know what the pedals do, what the steering wheel does, and practise to develop my intuition. Same with monads. I need to know what do notation does, what some basic operations do, and then I need practice to develop my intuition.


I disagree. If you don’t understand Monads then you loose a lot of opportunities to write clean elegant code in pure functional languages. I wrote my own Monads all the time in Haskell. However if you are not using a pure language then I agree.


I think a lot of it depends on what you mean by "understanding Monads". If "understanding Monads" means knowing where they fit in category theory (not simply where they fit in the Haskell typeclass hierarchy) that is absolutely not necessary to be productive in Haskell - I've worked with effective, professional Haskell programmers who have no idea what adjunction is.

Understanding how things fit together behind the interface well enough to implement it isn't a high bar and (as you say) has a pretty high payoff as you work on slightly larger projects.


Yep I agree.




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

Search: