For some reason everyone likes to talk about Monads, but really the other types here are just as interesting. For example, Applicatives are less dynamic than Monads in that you can't `flatMap`/`bind` to decide on the "next" thing to evaluate based on the previous value, but in exchange you get a "static" tree (or graph) of Applicatives that lends itself much better to static analysis, optimization, parallelism, and so on.
IIRC Haxl (https://github.com/facebook/Haxl) uses Applicatives to optimize and parallelise remote data fetching, which is hard to do with Monads since those are inherently sequential due to the nature of `flatMap`/`bind`. My own Mill build tool (https://mill-build.org/) uses an applicative structure for your build so we can materialize the entire build graph up front and choose how to parallelize it, query it, or otherwise manipulate it, which is again impossible with Monads since the structure of a Monad computation is only assembled "on the fly" as the individual steps are being evaluated. "Parallel Validation" where you want to aggregate all failures, rather than stopping at the first one, is another common use case (e.g. https://hackage.haskell.org/package/validation or https://typelevel.org/cats/datatypes/validated.html)
Monads seem to have this strange aura around them that attracts certain kinds of personalities, but really they're just one abstraction in a whole toolkit of useful abstractions, and there are many cases where Applicative or some other construct are much more suited
> Monads seem to have this strange aura around them that attracts certain kinds of personalities
Historical accident.
There was a time, not very long ago, when we didn't know applicative functors were a useful separate subset of monads. We thought full monads were needed for all the neat things that applicatives are sufficient for.
During this time, lots of ink was spilled over monads. Had we invented applicative functors a little earlier, they would probably have gotten more of the spotlight they deserve.
-----
I also think people underappreciate the humble semigroup/monoid. But this is not historical accident, it is just that it seems to simple to be useful. But it is useful to be able to write functions generic over concatenation!
Indeed it was not long ago that in the language there was no relationship at all between the Applicative class and the Monad class. And then one release Applicative was made the superclass of Monad. That's the reason why we have sequence and sequenceA, sequence_ and sequenceA_, liftM and fmap, ap and <*>, liftM2 and liftA2, return and pure, traverse and mapM etc. All these pairs of functions do the same thing but are duplicated for historical reasons.
This historical accident has, IMO, made the language harder to teach.
> Monads seem to have this strange aura around them that attracts certain kinds of personalities
I don't know if it's a matter of personality or aura. Monads are the first unfamiliar/complicated abstraction you're bumping into when learning Haskell. You can't do anything IO without monads, and they're not straightforward like functors or monoids. This is probably why there are more discussions about monads.
Unfortunately, while you may not have appreciated the tone of the Haskell interaction, they are correct in their assessment from a factual perspective. This explanation propagates a number of misunderstandings of the topics well known to be endemic to beginners.
In particular, I observed the common belief that functors apply to "containers", when in fact they apply to things that are not containers as well, most notably functions themselves, and it also contains the common belief that a monad has "a" value, rather than any number of values. For instance, the "list monad" will confuse someone operating on this description because when the monad "takes the value out of the list", it actually does it once per value in the list. This is the common "monad as burrito" metaphor, basically, which isn't just bad, but is actually wrong.
I'm not limiting it to these errors either, these are just the ones that leap out at me.
I agree. The "container" intuition for Monads leaves you stuck when you try to contemplate IO (or even Promises, these days), because the "bind" operator looks like it does something impossible: extract "the" `a` from the `IO a`, when you have no idea what it is. (Trust me, I spent a long time stuck at this point.) Better to think of Monad as "Applicative + join" (you need Applicative to get `pure`).
If you think of Monads in terms of `fmap` + `join :: Monad m => m (m a) -> m a`, then you don't need to imagine an "extraction" step and your intuition is correct across more instances. Understanding `join` gives you an intuition that works for all the monads I can think of, whereas the container intuition only works for `Maybe` or `Either e` (not even `[]`, even though it _is_ a container). You can define each of `>>=`/`join`/`>=>` in terms of `pure` + any of the other two, and it is an illuminating exercise to do so. (That `class Monad` defines `>>=` as its method is mostly due to technical GHC reasons rather than anything mechanical.)
I prefer the "join" approach for beginners too, but >>= has become so pervasive that I feel bad trying to explain it that way. Turning people loose on monad-heavy code with that approach still leaves them needing to convert their understanding into >>= anyhow.
One does wonder about the alternate world where that was the primary way people interacted with it.
Bartosz Milewski argues that we can think of functions etc. as containers as well, if you check out his YouTube lectures on Category Theory for Programmers. Lists and functions "contain" a type.
A term's utility comes from its ability to separate things into different categories. A definition of "container" that includes everything is therefore useless, because if everything a container, there is no information in the statement that something is a container.
In Bartosz's case he's probably making the precise point that we can abstract out to that point and that at a super, super high level of category theory, there isn't anything that isn't a container. However, that's a didactic point, not a general truth. In general we programmers generally do mean something by the word "container", and functors can indeed include things that are therefore not containers.
Moreover, I would say it's not what the author was thinking. The author is not operating on that super high level of category theory.
One can start with a partial explanation and expand it cover all the cases as learning progresses. This is how most learning takes place. I expect your primary school teachers introduced numbers with the natural numbers, instead of, say, transfinite numbers. Students learn Newtonian physics before relativity. It's completely fine to build an understanding of monads as operating on containers, and then expand that understanding as one encounters more cases.
In general, in abstract mathematics no analogy or "intuitive concept" of something will ever replace the rigorous definition. That doesn't mean that imperfect analogies can't be useful, though. You just have to use them as a starting point instead of stopping there.
I think the container analogy can be useful up to a point. There is (potentially) something of value wrapped in another type (e.g. an integer "wrapped in" IO) and we usually cannot access it directly (because of various reasons: because IO is special, because a list may be empty, etc.), but we can string together some operations that manipulate the contents implicitly.
Thinking too concretely about monads as boxes might make the behavior of the ListT monad transformer seem a bit surprising... unless you were already imagining your box as containing Schrodinger's cat.
I can definitely understand the author taking offense to the interaction, but now that a lot more programmers have had some experience with types like Result<T> and Promise<T> in whatever their other favorite typed language with generics is, the box/container metaphors are probably less helpful for those people than just relating the typeclasses to interfaces, and pointing out that algebraic laws are useful for limiting the leakiness of abstractions.
Functions are just containers of calculations (the whole “code is data”).
I don’t know why lists as values in a container would be confusing. Lots of very popular languages literally have box types which may not be exactly the same, but show that expecting containers to potentially commission complex data isn’t unusual.
One source for confusion around lists is that the list monad is often used to model non-determinism, rather than just "many things". If you're thinking about non-determinism, a list is akin to a container of one item when you don't precisely know which item it is, but do know it's one of zero or more candidates.
The most widely recognised example, IMO, would be monadic parser combinators. "A parser for a thing, is a function from a string, to a list of pairs of strings and things."
I think over the recent years, there's been a rise in typed languages that support functional programming like TypeScript and Rust. It will be interesting to see if this trend continues in the context of AI assistant programming. My guess is that it will become easier for beginners, and the type systems will help to build more robust programs in cooperation with AI.
Yes, type checking works very well with AI since typing provides a step of verification. The more precise the type system is, and the more guarantees you can have. At some extreme, you can entirely specify the program with types. So if the program type checks, it is guaranteed to implement its spec and you can trust the AI. I assume the AI will have a hard time to write the code though. But I had very good results in Rust, less in Haskell. I think it's also because the Rust compiler gives more meaningful error messages, which helps the AI to iterate.
Even in early 2025, LLMs are already the most powerful type inference algorithm. Why would they need a static type system in 2030?
My guess is it'll be the opposite: I suspect compared to humans, LLMs will make fewer type errors, and more errors that are uncaught by types. Thus I expect type systems will be of lower value to them (compared to humans), leading to a shift toward dynamic languages and the possible extinction of typed languages.
The alternative I could imagine is moving toward Haskell-like languages with MUCH stronger type systems, where higher-level errors ARE type errors.
My one concrete observation in this direction: "press . to see valid options" behavior is a traditional strong point of typed languages. And interestingly, proved to be one the first thing that early/dumb LLMs were actually pretty good at. I believe that indicates LLMs are relatively good at type inference (compared to other things you can ask them to do), and we should expect that to continue being a strong point for them.
In working with Cline in both TypeScript and JavaScript, I find the LLM making tons of errors it has to go and fix in a future iteration, but virtually none of them are type errors.
I suspect LLMs are relatively good at duck-typed languages because they have a much bigger working memory than humans. As a result, the LLM can hold in working memory not just e.g., the function argument in front of them, but also all the callers of the function, and how they used the variable, and callers of the callers, and thus what "duck-type" it will be.
A system that can do this level of automatic type inference doesn't necessarily benefit from a formal, static, compile time type system.
A well-typed program provides a soundness proof that can be straightforwardly verified by just compiling the program. Even in a humongous codebase in a slow-to-compile language, this is cheaper than e.g. running an LLM on your codebase every time you push a commit (especially if you use incremental compilation). Type systems, even simpler ones, just give a lot of bang for the buck.
> Why would they need a static type system in 2030?
Why do many people talk about type systems as if they're only a safety guard?
To me that's never the main role of type systems. I don't know what's the word for it, but types allow me to read the code, on a high level. Sure AI will write them but as long as software engineers exist, we still have to read the code. How do you even read code without types? Comments? Unit tests? Actual implementation?
> LLMs will make fewer type errors, and more errors that are uncaught by types
> extinction of typed languages
Don't you find these contradictory? If LLM increases the rate of error uncaught by types, then type systems or the usage of them should catch up, otherwise there is no magical way for software to get better with LLM.
In the current state of LLM, the type system (or lsp and/or automated tests) is what allows the "agentic" AI coder to have a feedback loop and iterate a few times before it hands off to the programmer, perhaps that gives the delusion that the LLM is doing it completely without type system.
I feel like Haskell is easier to use than it is to explain, and in my experience a lot of these kind of tutorial / explanations actually make things seem harder and more complicated than just working with the concepts and observing what they do. (This one included.)
I'm not familiar with Haskell and am really, really struggling to follow the article.
In the case of the functor, the author doesn't explain in technical, specific enough terms the difference between "open the box, extract the value out of it, apply the function, and put the result back in a box" and "apply a function to a box directly; no need to perform all the steps ourselves." I have no idea what 'apply a function to a box' even means.
> That’s the essence of functors: an abstraction representing something to which we can apply a function to the value(s) inside
The error in this sentence garbles its meaning beyond recovery. "We can apply a function" governs two prepositional phrases that are semantically and syntactically identical: "to which;" "to the value(s) inside." There's no way to resolve the meaning of one without rendering the other incoherent.
The number one mistake is everyone trying to explain a Haskell concept to the general population makes is using Haskell. If someone already knows Haskell there is a good chance they know there concepts. Don't use Haskell as the language, use js to explain it.
The number two mistake people make is being aware of the number one mistake so they go write yet another Monad tutorial in Javascript (or Java or whatever...). Which is why there are so many damn Monad tutorials, all saying pretty much the same thing.
The distinction is that in general “opening a box and extracting the value” makes no sense, as it's not a thing that can be done in general. If your box is a Maybe, there might not be a value to extract. If it's a list, there might be zero or multiple values. It only ever makes sense to map over the contents of the box, replacing the values with their image under the map.
To try to answer your first question, coming form someone who is also not an expert in Haskell or monads.
"apply a function to a box directly; no need to perform all the steps ourselves."
The box doesn't change, and it also doesn't matter what's inside of it. You are attaching the function to the box, who later knows how to apply it to itself. If you were to open the box, you would need to know how to handle all the possible contents. It's key here that you are only handling a box and nothing else.
Every “hard” concepts I’ve seen in Haskell is immediately clear to me if explained in almost any other language. The hard part is Haskell, not the concept.
Usually I’m left wondering why whatever-it-is even has a name, it’s so simple and obvious and also not that special or useful seeming, it’d never have occurred to me to name it. I guess the people giving them names are coming at them from a very different perspective.
Exception: type classes. Those are nice and do need a name.
Haskell has a type system that lets these things be directly useful in ways they cannot be in many other languages.
You can't, in Java, declare anything like "class Foo<F<T> extends Functor<T>>", or use a similar generic annotation on a method: you can't have an unapplied type-level function as an argument to another type-level function.
These things get a name in Haskell because they can be directly used as useful abstractions in their own right. And perhaps because Haskell remains very close to PL research.
Yes, I really need a real word Haskell project simple enough to understand all the math concept. Like, I don't know when to implement the Monad type-class to my domain data types. For example, taking the twitter example, if I have Tweet data type:
- should I implement the Monad, Applicative or Functor type class?
- How would that help in the big picture?
- What if I don't do it?
All these funny example of boxes, burritos or context doesn't not help me solve problems.
Take for example Monoid, I understand (partially maybe) that it useful for fold (or reduce) a list to a single value.
The problem with Monads etc. is that they're simple concepts with extremely confusing names. Monad should be FlatMappable. Once it has the correct name it barely even needs an explanation at all.
I've seen this opinion before but disagree with it. There are maybe five names to learn. They relate to the actual concepts, allowing you to expand your knowledge.
One issue with that is that you can write Flatmap in a way that doesn't obey the Monad axioms. And once you write out what it means to be 'correctly' flatmappable you've recreated the Monad axioms.
Though it would help if more people were aware that a 'nice' way to 'unnest' a functor (F F x -> F x) is really all that it takes to have a Monad.
FlatMappable doesn't capture what a monad is. For instance, you can do async programming using monads. Doesn't relate to FlatMappable.
I think you don't see the need for a new name if you don't grasp the concept. It's like in mathematics, you have tons of algebraic structures, like monoid, groups, fields, rings. They all represent categories of things which share some properties. You don't want to name the category by a one of its representatives, that would defeat the purpose of introducing an abstraction.
I don't know, I think the fact that you can use FlatMappables to do async programming and pure IO etc. doesn't mean you have to capture all of the potential uses in the name.
I mean... you can use timer interrupts to do preemptive multi-threading but we don't feel the need to give them a confusing name.
Monads in Haskell is just a way to combine two functions into one when the output of the first function doesn't match the input of the 2nd function.
That's all there is to it.
It is not a way to "box values". Yes you can use it for that if the functions you combine happens to operate on "boxed values" (like Maybe) but that has nothing to do with the fundamental idea of what a monad is.
And yes there are "monad rules" that ensures that combining the functions "makes sense". But people sometimes use monads in Haskell that doesn't actually follow those rules. But it works fine anyways for whatever they are doing.
Part of why monads are not interesting to talk about is that they’re generic enough that most explanations are incomplete, and sufficient explanations are boring and unhelpful.
But the biggest reason is that they’re sort of intuitive, plenty examples exist. And then at some point someone tells you that those things are monads, but it’s in the kind of way that social psychologists make up some fancy word for crap we all know about in our gut.
Nobody gives a shit that a list is a monad, people give a shit that it’s a list. Anyone who’s written lisp or node or any nontrivial C program or anything with coroutines or anything with concurrency can and will tell you that, yeah, duh, control flow can be represented by a data structure. A couple more fancy “monad laws” and you have something that looks like other monads, and lists and if expressions and IO meld together. Ok, how unhelpful.
The fact that they are so generic is what makes people misunderstand them: They focus on 1 or 2 examples, without seeing that the same concept works in all kinds of other use cases.
People realize a list can be a monad, and they they imagine option and set are also monads. But then you have to tell them that the same applies to Future, and Either. That you can have a resource monad that closes resources.
This is when the fact that something is a monad starts to matter, because of generic concepts for transformers. Every language that has promises and lists will give you a way to turn a List[Promise[T]] into Promise[List[T]], written ad-hoc, but it doesn't have to be quite so ad-hoc. It's when you are stacking 3 or 4 different properties together that the abstract concepts matter. The lack of the abstraction is what makes some language have trouble doing more than just a little bit of functional programming, as going deeper becomes unmanageable without some help.
The helpful part is the ability to abstract over arbitrary monads. That's the thing that makes it worth identifying that it's a known and well-studied pattern.
Hmm. I've not yet seen a topical presentation which embeds a tweaked chatbot. Graphics, video, interactive graphics, each provide additional leverage beyond text. So too might "something to talk over the topic with". Something with a punch-list of insights to be conveyed, and misconceptions to be probed for.
Monad education is rich in flawed models and incomplete appreciation, and also in meta discussion of these. Might this lend itself to a interactive socratic-y tutor? Could the world use a... new and improved monad tutorial?
IIRC Haxl (https://github.com/facebook/Haxl) uses Applicatives to optimize and parallelise remote data fetching, which is hard to do with Monads since those are inherently sequential due to the nature of `flatMap`/`bind`. My own Mill build tool (https://mill-build.org/) uses an applicative structure for your build so we can materialize the entire build graph up front and choose how to parallelize it, query it, or otherwise manipulate it, which is again impossible with Monads since the structure of a Monad computation is only assembled "on the fly" as the individual steps are being evaluated. "Parallel Validation" where you want to aggregate all failures, rather than stopping at the first one, is another common use case (e.g. https://hackage.haskell.org/package/validation or https://typelevel.org/cats/datatypes/validated.html)
Monads seem to have this strange aura around them that attracts certain kinds of personalities, but really they're just one abstraction in a whole toolkit of useful abstractions, and there are many cases where Applicative or some other construct are much more suited
Historical accident.
There was a time, not very long ago, when we didn't know applicative functors were a useful separate subset of monads. We thought full monads were needed for all the neat things that applicatives are sufficient for.
During this time, lots of ink was spilled over monads. Had we invented applicative functors a little earlier, they would probably have gotten more of the spotlight they deserve.
-----
I also think people underappreciate the humble semigroup/monoid. But this is not historical accident, it is just that it seems to simple to be useful. But it is useful to be able to write functions generic over concatenation!
This historical accident has, IMO, made the language harder to teach.
I don't know if it's a matter of personality or aura. Monads are the first unfamiliar/complicated abstraction you're bumping into when learning Haskell. You can't do anything IO without monads, and they're not straightforward like functors or monoids. This is probably why there are more discussions about monads.
In particular, I observed the common belief that functors apply to "containers", when in fact they apply to things that are not containers as well, most notably functions themselves, and it also contains the common belief that a monad has "a" value, rather than any number of values. For instance, the "list monad" will confuse someone operating on this description because when the monad "takes the value out of the list", it actually does it once per value in the list. This is the common "monad as burrito" metaphor, basically, which isn't just bad, but is actually wrong.
I'm not limiting it to these errors either, these are just the ones that leap out at me.
If you think of Monads in terms of `fmap` + `join :: Monad m => m (m a) -> m a`, then you don't need to imagine an "extraction" step and your intuition is correct across more instances. Understanding `join` gives you an intuition that works for all the monads I can think of, whereas the container intuition only works for `Maybe` or `Either e` (not even `[]`, even though it _is_ a container). You can define each of `>>=`/`join`/`>=>` in terms of `pure` + any of the other two, and it is an illuminating exercise to do so. (That `class Monad` defines `>>=` as its method is mostly due to technical GHC reasons rather than anything mechanical.)
One does wonder about the alternate world where that was the primary way people interacted with it.
In Bartosz's case he's probably making the precise point that we can abstract out to that point and that at a super, super high level of category theory, there isn't anything that isn't a container. However, that's a didactic point, not a general truth. In general we programmers generally do mean something by the word "container", and functors can indeed include things that are therefore not containers.
Moreover, I would say it's not what the author was thinking. The author is not operating on that super high level of category theory.
I think the container analogy can be useful up to a point. There is (potentially) something of value wrapped in another type (e.g. an integer "wrapped in" IO) and we usually cannot access it directly (because of various reasons: because IO is special, because a list may be empty, etc.), but we can string together some operations that manipulate the contents implicitly.
I can definitely understand the author taking offense to the interaction, but now that a lot more programmers have had some experience with types like Result<T> and Promise<T> in whatever their other favorite typed language with generics is, the box/container metaphors are probably less helpful for those people than just relating the typeclasses to interfaces, and pointing out that algebraic laws are useful for limiting the leakiness of abstractions.
I don’t know why lists as values in a container would be confusing. Lots of very popular languages literally have box types which may not be exactly the same, but show that expecting containers to potentially commission complex data isn’t unusual.
The most widely recognised example, IMO, would be monadic parser combinators. "A parser for a thing, is a function from a string, to a list of pairs of strings and things."
The GP makes it pretty clear - the misunderstanding is that there is one value in a container. A list has many.
I think over the recent years, there's been a rise in typed languages that support functional programming like TypeScript and Rust. It will be interesting to see if this trend continues in the context of AI assistant programming. My guess is that it will become easier for beginners, and the type systems will help to build more robust programs in cooperation with AI.
My guess is it'll be the opposite: I suspect compared to humans, LLMs will make fewer type errors, and more errors that are uncaught by types. Thus I expect type systems will be of lower value to them (compared to humans), leading to a shift toward dynamic languages and the possible extinction of typed languages.
The alternative I could imagine is moving toward Haskell-like languages with MUCH stronger type systems, where higher-level errors ARE type errors.
My one concrete observation in this direction: "press . to see valid options" behavior is a traditional strong point of typed languages. And interestingly, proved to be one the first thing that early/dumb LLMs were actually pretty good at. I believe that indicates LLMs are relatively good at type inference (compared to other things you can ask them to do), and we should expect that to continue being a strong point for them.
In working with Cline in both TypeScript and JavaScript, I find the LLM making tons of errors it has to go and fix in a future iteration, but virtually none of them are type errors.
I suspect LLMs are relatively good at duck-typed languages because they have a much bigger working memory than humans. As a result, the LLM can hold in working memory not just e.g., the function argument in front of them, but also all the callers of the function, and how they used the variable, and callers of the callers, and thus what "duck-type" it will be.
A system that can do this level of automatic type inference doesn't necessarily benefit from a formal, static, compile time type system.
Why do many people talk about type systems as if they're only a safety guard?
To me that's never the main role of type systems. I don't know what's the word for it, but types allow me to read the code, on a high level. Sure AI will write them but as long as software engineers exist, we still have to read the code. How do you even read code without types? Comments? Unit tests? Actual implementation?
> LLMs will make fewer type errors, and more errors that are uncaught by types
> extinction of typed languages
Don't you find these contradictory? If LLM increases the rate of error uncaught by types, then type systems or the usage of them should catch up, otherwise there is no magical way for software to get better with LLM.
In the current state of LLM, the type system (or lsp and/or automated tests) is what allows the "agentic" AI coder to have a feedback loop and iterate a few times before it hands off to the programmer, perhaps that gives the delusion that the LLM is doing it completely without type system.
In the case of the functor, the author doesn't explain in technical, specific enough terms the difference between "open the box, extract the value out of it, apply the function, and put the result back in a box" and "apply a function to a box directly; no need to perform all the steps ourselves." I have no idea what 'apply a function to a box' even means.
> That’s the essence of functors: an abstraction representing something to which we can apply a function to the value(s) inside
The error in this sentence garbles its meaning beyond recovery. "We can apply a function" governs two prepositional phrases that are semantically and syntactically identical: "to which;" "to the value(s) inside." There's no way to resolve the meaning of one without rendering the other incoherent.
The number two mistake people make is being aware of the number one mistake so they go write yet another Monad tutorial in Javascript (or Java or whatever...). Which is why there are so many damn Monad tutorials, all saying pretty much the same thing.
"apply a function to a box directly; no need to perform all the steps ourselves."
The box doesn't change, and it also doesn't matter what's inside of it. You are attaching the function to the box, who later knows how to apply it to itself. If you were to open the box, you would need to know how to handle all the possible contents. It's key here that you are only handling a box and nothing else.
Usually I’m left wondering why whatever-it-is even has a name, it’s so simple and obvious and also not that special or useful seeming, it’d never have occurred to me to name it. I guess the people giving them names are coming at them from a very different perspective.
Exception: type classes. Those are nice and do need a name.
You can't, in Java, declare anything like "class Foo<F<T> extends Functor<T>>", or use a similar generic annotation on a method: you can't have an unapplied type-level function as an argument to another type-level function.
These things get a name in Haskell because they can be directly used as useful abstractions in their own right. And perhaps because Haskell remains very close to PL research.
Gabriel Gonzalez - “A bare-bones Twitter clone implemented with Haskell + Nix” @ ZuriHac 2020 https://www.youtube.com/live/Q3qjTVcU9cg
- should I implement the Monad, Applicative or Functor type class?
- How would that help in the big picture?
- What if I don't do it?
All these funny example of boxes, burritos or context doesn't not help me solve problems.
Take for example Monoid, I understand (partially maybe) that it useful for fold (or reduce) a list to a single value.
Though it would help if more people were aware that a 'nice' way to 'unnest' a functor (F F x -> F x) is really all that it takes to have a Monad.
I think you don't see the need for a new name if you don't grasp the concept. It's like in mathematics, you have tons of algebraic structures, like monoid, groups, fields, rings. They all represent categories of things which share some properties. You don't want to name the category by a one of its representatives, that would defeat the purpose of introducing an abstraction.
I mean... you can use timer interrupts to do preemptive multi-threading but we don't feel the need to give them a confusing name.
That's all there is to it.
It is not a way to "box values". Yes you can use it for that if the functions you combine happens to operate on "boxed values" (like Maybe) but that has nothing to do with the fundamental idea of what a monad is.
And yes there are "monad rules" that ensures that combining the functions "makes sense". But people sometimes use monads in Haskell that doesn't actually follow those rules. But it works fine anyways for whatever they are doing.
But the biggest reason is that they’re sort of intuitive, plenty examples exist. And then at some point someone tells you that those things are monads, but it’s in the kind of way that social psychologists make up some fancy word for crap we all know about in our gut.
Nobody gives a shit that a list is a monad, people give a shit that it’s a list. Anyone who’s written lisp or node or any nontrivial C program or anything with coroutines or anything with concurrency can and will tell you that, yeah, duh, control flow can be represented by a data structure. A couple more fancy “monad laws” and you have something that looks like other monads, and lists and if expressions and IO meld together. Ok, how unhelpful.
People realize a list can be a monad, and they they imagine option and set are also monads. But then you have to tell them that the same applies to Future, and Either. That you can have a resource monad that closes resources.
This is when the fact that something is a monad starts to matter, because of generic concepts for transformers. Every language that has promises and lists will give you a way to turn a List[Promise[T]] into Promise[List[T]], written ad-hoc, but it doesn't have to be quite so ad-hoc. It's when you are stacking 3 or 4 different properties together that the abstract concepts matter. The lack of the abstraction is what makes some language have trouble doing more than just a little bit of functional programming, as going deeper becomes unmanageable without some help.
Monad education is rich in flawed models and incomplete appreciation, and also in meta discussion of these. Might this lend itself to a interactive socratic-y tutor? Could the world use a... new and improved monad tutorial?