I've referred to this blog post at least twice a year since I read it 6 or 7 years ago. Not because I need to explain monads, but because the "monad tutorial fallacy" [1] is an incredibly useful concept to be aware of when trying to convey knowledge. I encourage you to read about it :)
That reminds me of giving the advice, "Just do what comes naturally," to beginners. That's the last advice someone who has spent years building up an intuitive model needs, but it's terrible for beginners.
I have never seen so many kids harmed by bad pedagogy as “just do what comes naturally”. My daughter’s PE teacher gaslit her into thinking she was left handed with this advice and just created the belief that she was inept.
99% of what we do doesn’t come naturally, that’s why we are the most adaptable and successful megafauna in the history of the planet.
Learning is the essence of humanity.
Edit: you will be happy to know I fixed my daughters throwing technique with evidence based double blind tests to show her I knew what I was talking about and then taught her how to throw.
This is advice often given to me to try to entice me to dance, which I hate doing, and nobody believes me that "what comes naturally" is getting off the dance floor and not dancing!
Haha, yes. My confidence dancing was terrible for years because of similar advice. Turns out all the girls and boys who were good had been practicing! Who would have thought.
Recently I've been thinking the important thing is that advice is always highly context sensitive, so throwing out "good advice" without qualification or discernment leads to a lot of harm.
Take (again) dating advice: Saying something like "you shouldn't put so much emphasis on someone's looks" may help someone who is unsuccessful at dating and puts an inordinate emphasis on looks in their selection process. But if someone who already had a balanced view on looks that hears the advice, they may take it to heart and try dating people they have no physical attraction to and find less success.
tldr: Pottery class, 2 groups. 1 group instructed to make the perfect pot (graded by quality of 1 best pot). 1 group instructed to make a bunch of pots (graded by pounds of clay used).
At the end of semester, the group who was trying to make as many pots as possible also produced the single best pot of the class.
Lesson: You need reps. Lots of reps. You really do just gotta do what comes naturally, get feedback, then do more. Doing is the only way to learn tacit skills.
You can read about the best way to ride a bike for 10 years and achieve nothing. Or you can spend an afternoon failing a lot until you can ride a bike. Software architecture and code structure are similar. You just gotta produce piles upon piles of code and see what works.
The problem with this is that you will often end up with something that seemingly works (barely), but the way you got there precludes you from advancing further.
I heard this presented as a photography class. Doing a quick search shows that it's from a memory plot in "Atomic Habits" by James Clear.
The story features Jerry Uelsmann and having his class of photographers either work by quantity or quality of shots.
The specific example I remember was "you can only take 1 shot" vs "take as many as you can"; and the lesson being /the group that did many reps had ultimately better photos/.
That is "do what comes naturally for a long time". Is education about efficiently and effectively transferring thousands of years of invention and practice insights or about letting everyone build civilization from first principles?
For a long while now, I've been trying to write a monad tutorial I'm actually happy to give people as an entry point, and that point is precisely why I haven't put anything out there yet. It's also the reason I keep trying: there's something important to be learnt there in terms of communicating tricky concepts.
I think the lesson is that tricky concepts have to be taught by example.
Some theoretically-bent people can read a definition and generate their own examples, but most people I've met need to be presented with and taught one example at a time. After a while, they just get it and the abstraction falls into place.
> I think the lesson is that tricky concepts have to be taught by example.
The bigger insight, for me, is that they also have to be taught by counter-example.
E.g. it's easy to find Functors that are neither Applicatives nor Monads (usually, because you can't produce a reasonable definition for `return`). It's surprisingly hard to find a non-contrived example of a Functor that is also an Applicative, but *is not* a Monad. Without that counter-example, it's very tricky to build the intuition for where the boundary lies.
Either would short circuit on the first validation error. There’s two interesting things here though: there are no data dependencies between different validations (so we don’t _need_ a monad), and it’s useful to accumulate errors to know if both option1 and option3 failed to validate (so we don’t _want_ the Either monad’s short circuiting behaviour). The implementation is pretty straightforward, too!
Some theoretically-bent people can read a definition and generate their own examples
This is what mathematical training taught me to do. It's something I wish everyone could learn to do but high school is too short to teach it to most people.
A technique I've had a reasonable amount of success with when teaching formerly imperative programmers is focusing on the constraints that force something like a monad to exist (the "you could have invented it yourself" teaching approach):
1. One of the main points of functional programming is that your computations are the results of pure functions -- enabling niceties like thread-safety, type-safety, ....
2. That view of the world is imperfect because some interactions (like responding to user input) require a _sequencing_ of events.
3. Imperative languages accomplish that task easily. They just write the events in the sequence they want them executed. With constraint (1) though, your only option for sequencing is function composition. Whatever interface you choose, it _has_ to use composition somehow.
4. Monads are one of many possible solutions, and they're the one we happen to have standardized on. They're convenient because you can wrap up all that compositional nonsense into something that feels a lot like the mutable objects you're used to when you actually apply them in your code.
That explanation skips over a lot of details, but it helps people build the initial spark of intuition to attach all the rest of the ideas onto. The mechanics of a monad, their capabilities, the available syntax, ..., don't seem to be the main stumbling block in my experience.
Yes, this post is classic. My answer to Brent's "monad tutorial fallacy" is https://mightybyte.github.io/monad-challenges/. It was inspired by The Matasano Crypto Challenges that Thomas Ptacek & others created awhile back which, instead of trying to teach you cryptanalysis, guides you down the path of actually doing realistic cryptanalysis with a series of challenges.
I always felt like the one thing I wanted in a monad tutorial was a list of things that aren't monads because they follow all the monad rules except for one, for each rule.
Monads are so easy. That's why there are 2,000 articles online explaining how easy they are. :)
Okay hear me out:
I don't like exception handling, because it doesn't feel "pure" and consistent with the rest of my program flow. If any errors happen, I want to know what they are so I can plan ahead. You might say "well, just catch your exceptions dammit". But see, what if I forget to catch an exception right away and it propagates? Also, other than Swift, I don't know of any programming language that let's me explicitly mention if a function could throw an exception. In Python, for example, you can't just do Optional[str], because returning None is different from raising an exception. So by just looking at a function type signature, one can't know if it throws, which means you never know if you should use try/catch or not.
But let's say you take care of all edge cases and return None in those bad paths instead of raising exceptions. Let's also assume that Python does't suck and it never raises exceptions by its own standard libraries (e.g., json throws). The problem is: How do we know if the function returned None because the evaluation was None, or because it failed at some point?
So a better approach is to explicitly say the function returns "something" which may or may not be there. If it's there, then the function worked correctly, even if the value is None. If it's not there, then the function failed. Go does this. Rust does this better. Haskell has had this since ages thanks to Monads.
I like to think of monads as wrappers around data. In Python, I simply write a Result monad (similar to Rust). In Haskell, this is called a Maybe monad.
> Also, other than Swift, I don't know of any programming language that let's me explicitly mention if a function could throw an exception.
Checked exceptions in Java. I both like and hate them.
They force the function to be explicit about what exceptions they can raise. If you call a function that raises a checked exception, you must either explicitly handle the exception, or mark that it is propagated up by adding the same exception to the calling method signature.
However, they cause a lot of pain for working with higher order methods (map, filter, flatmap, etc). Because they change the signature of the method (and therefore the interface it can satisfy), you need to use a lot of generic variants of higher order methods to accept them, or like a lot of libraries, you end up writing wrapper functions that convert the checked exceptions into unchecked runtime exceptions, such that they don't modify the method signatures. This then leads to a lot of weird code, and uncaught exceptions at runtime, taking down the application.
The problem here isn't checked exceptions as such, it's that they are not first-class, so you can't write generic code in terms like "I take F and throw all E that F throws plus X".
> I like to think of monads as wrappers around data
Technically they're a wrapper around a data type. Maybe Foo is a Foo that might not be there, [Foo] is zero or more Foos, etc. Which is actually describing a Functor, but Monads are also Functors, the "monadness" comes from the particular functions like `bind` (or `flatMap`) that work on them.
My favorite approach is from Functional Programming in Scala, which doesn't mention monads until chapter 11. But it has you implement a bunch of collections with `map`, `map2`, `unit`, and `flatMap`, and exercises that repeatedly show how useful this interface is. So by the time you get to it, the definition of Monad feels obvious, as well as Functors & Applicatives.
What’s hard is higher-order functions, parametric polymorphism, and ad-hoc polymorphism via typeclasses. Understanding any one of these is hard. Understanding all three is harder. Understanding all three applied simultaneously is harder still. That’s what monads are.
On top of all that, understanding “monads” doesn’t mean too much. IO does something, Maybe does something else, StateT something else.
But once you understand all that, monads are easy. Maybe monad analogies helped somebody understand all that, but they didn’t help me.
Tongue in cheek but seriously, I was pretty happy with my working understanding of monads and now I’m questioning it. Would love to hear you elaborate or link references so I can learn more
It’s pretty perfect this comment stream has gone from a couple jokes to people (poorly) explaining monads. Basically we’ve gone from the Monads are like burritos joke to the examples of the monad tutorial fallacy blog post in 91 comments.
The monad of list is you flatmap a list on a list and instead of getting a list of lists, as you would if you just mapped, you get a single flattened list
I often forget what a monad is, and need to keep reminding myself "remember list.map and Option.and_then? if so, then you don't need to know what a monad is". But will try thinking about burritos next time to see if it helps.
I still don't get what they have to do with i/o in Haskell, but am ok with i/o in Haskell being one of my life's forever unknowable mysteries.
I'd argue that if you know what Option.and_then does, you do know what a monad is. and_then, known as flat_map or bind in the generic case, is the quintessential operation of a monad.
One of my biggest gripes with Rust (and other languages) is lack of a good monad abstraction (and accompanying syntactic sugar). But I also acknowledge that this is a "I have done too much category theory" problem and and_then is probably a more reasonable name to those not familiar with this particular area of maths.
> I still don't get what they have to do with i/o in Haskell, but am ok with i/o in Haskell being one of my life's forever unknowable mysteries.
They don't. Not really, anyway. IO happens to be a monad, but that's not really relevant to it being used as an abstraction around side effects. Haskell being a pure language is at odds with the necessity of some side-effects, such as printing to the console. The solution is to construct a "to-do list of actions" or "blueprint" for desired side-effects. This is an instance of IO. Any such blueprint that is assigned to the name "main" is executed. That's it.
The monad part comes in when you want to construct this blueprint. It so happens that one reasonable way to do this is via flat_map.
> One of my biggest gripes with Rust (and other languages) is lack of a good monad abstraction (and accompanying syntactic sugar). But I also acknowledge that this is a "I have done too much category theory" problem and and_then is probably a more reasonable name to those not familiar with this particular area of maths.
I'm happy that Rust has iterators and I don't have to think about what a monad is at all.
The way I think about it is that the general monadic structure is that the results of an operation can cause new operations to happen with equivalent complexity. This is exactly what "flat map" is.
So what the "IO monad" in Haskell represents is the general principle across programming languages where you can, say, read a file, then for each line in the file read another file or otherwise do additional I/O -- and this can spiral out in an unbounded fashion.
As developers we can observe the monadic structure of operations everywhere (and generally seek to avoid it! Monads are fundamentally too powerful!) But whether it is worth encoding the general idea of monadic structures into the type system is a separate question. Haskell says yes, most others say no. There are good reasons both ways. (One underappreciated reason is that exposing a hierarchy of typeclasses/traits introduces API stability considerations. When Applicative was inserted in between Functor and Monad in Haskell, the whole ecosystem had to be updated.)
(Tries to fight the urge to explain, fails) it’s a construct to sequence async interactions, one after the other, without blocking. It’s very similar to c# or python’s await
Is something that is so inherently hard to explain while giving it justice truly practical or even worth it? If you are in a room with 10 devs, how many will have a deep understanding of monads? And if it is expected to be only a few, is it really constructive to have it in the codebase? Or is it just going to trip people up and be misapplied.
I think the real question is more - is the language of category theory really worth it here.
Monads as a concept is basically just a fancy version of a wrapper class (if you in OOP land). Do we really need the cognitive overhead of advanced mathematics to explain that?
This comment would be stronger with some kind of examples or other reasoning about the kind of problems this avoids.
In particular, the dichotomy you created which paints the two options as “advanced mathematics” and “intuition” seems to need some proof that there is no third option. Since the original poster mentioned OOP, that would be a good place to start with some example of a problem which is hard to avoid following good OOP practice.
It’s not a dichotomy, it’s a fact. Lucretius had the idea that the world was comprised of atomos (by way of Epicurus). Without mathematics and science he didn’t have a theory. His intuitions got him far and maybe it set the stage for Dalton but Lucretius’, The Nature of Things wouldn’t be the foundation of discoveries that leads to new understanding.
In programming you end up with JavaScript Promises instead of proper monads because of similar appeals to intuition. A monad obeys certain laws upon which further abstractions can be built. Promises do not obey those laws and so one cannot benefit from them. A programmer has to learn what a Promise is and how it behaves. You learn what a monad is once and anything that is a monad behaves the same way.
The reason why it’s important to use the proper names is so that people can find the original definitions and theorems. You don’t have to be a category theorist to use a monad. You can have a basic understanding, as I do, and get by fine. However when you want to understand the theorems to build your own abstractions it’s great being able to go straight to the source.
Simon Peyton-Jones has a good explanation for why Haskell took this route in his talk, Escaping the Ivory Tower.
Good OOP practise does involve writing things down. Just look at design patterns books or c2 wiki.
My contention is not that writing things down or naming abstractions is bad. On the contrary i think that is a good thing.
I don't even think monads are a bad abstraction. Monads are a fine abstraction. Its the baggage people carry with it that is the problem.
My main contention is that people are cargo culting the way monads are explained in category theory and applying that to computer programming. This sort of works, but generally not that well because its copied from a field with very different goals.
Math wants to prove things in the most general way possible. This lets them explain & discover the deepest possible patterns. They trade complexity to achieve this goal.
Computer programming aims to solve problems efficiently in a way that is efficient and easy to modify. Complexity is the enemy of this goal.
Even in math, you might prove that some object is a monad or whatever, but you will still generally work from its normal definition and not from the definition of a monad.
But the concept of monad as used in math is really quite different from how it is used in programming (like the object is the same, but the point of all the mathematical machinery of category theory is essentially to make proofs that apply to a wide variety of objects that share some abstract property. That is not the primary activity we do in programming. We do it a little bit when it comes to designing abstractions, but for the most part the mental abstractions are quite strained here).
The language of category theory is great for proving things. Its esecially great for proving things in very general ways.
Computer programming (to be clear: different from computer science) is not math. Sure programs are proofs (/me waves at howard-curry) but they are not the types of proofs math is generally interested in. Its the same task applied to different goals. The tools for both might work in both contexts, but they are not going to work equally as well.
That's not to say we shouldn't try and formalize things - we should. Category theory is just not a good design pattern language for typical programming tasks. It wasn't designed with that goal in mind, it does a bad job with it. Worse, it sounds very learned, so pseudo-intellectuals use it to gate-keep.
Personal feelings about gate keeping aside, it’s better to have a name for a thing that’s in common use than to make up your own and confuse everyone.
You don’t have to be a gatekeeping, academic elitist to understand how to use a monad. I use them every day at work and I program on my stream where I’m using them all the time.
What’s nice about it is that if I want to go on that journey and learn more about monads so that I can build my own abstractions upon them I can go right to the source definitions.
I don’t have to go look up what a BurritoWrapper is and all of the methods designed for it that only work with whatever the author designed them for. Once I know I have a monad I know how to use it.
Curry-Howard is super cool and we should formalize things more.
Everything as a concept in CS is just a fancy version of sticking lambdas together, or literally connecting dots with arrows. Having more specific names for the fancier arrangements helps a lot.
You don't need to have a deep understanding of monads to use them. Plenty of languages are built on concepts that their users aren't expected to understand thoroughly.
I don't think you need to understand a concept deeply to use it. The "do..." syntax in Haskell is something that comes naturally to many programmers. It is introduced only at the end of a Haskell course since it uses monads, but many iterative-style programmers switch to that syntax exclusively afterwards. And it actually takes a while to construct the desugared monad syntax.
For a common example of this phenomenon- I took a look into the innards of printf to see how printf("%f",...) and printf("%g",...) works. I am still clueless how it actually works. Does not prevent even beginners from using these.
==
Also for what it is worth, I think the most useful analogy of monads is "monads are pipes with types", even though it does not give a full understanding of bind.
If your codebase is written in F#, OCaml or Haskell then it would be surprising if there were no use of monads as it is a pretty common design pattern in a functional language.
Explanations with bad analogies are really a very often problem.
Although this author matched monads with burritos relatively well. Except for the purpose - we know the purpose of burritos, but this does not help to understand the purpose of monads (for programming, in particular).
But sometimes a concept is named after a bad analogy by the concept authors (and probably even invented after that bad analogy, although maybe the authors just use poor name because they fail to clearly recognise the essence of their solution). Like mixins. I satirise that in my Mixin FAQ
Q: What is a mixin?
A: Mixin allows to inject functionality into classes. Mixins first appeared in the Flavors system and were inspired by an ice cream shop offering a basic flavor of ice cream (vanilla, chocolate, etc.) with a choice of optional "mix-in" ingredients like nuts, cookies, candies, etc.
Q: Can I mix-in a ServerSocket into a ColorPicker?
Ok, so everyone confusing me with bad explanations of monads was just confused all along and they are simple. Is that all this was ever all about then?
I think it is best just to provide the mathematical definition, provide the commutative diagrams that explains the concept graphically and then provide an example for computer scientists in Haskell: https://beuke.org/monad/
I have a general rule about abstraction. If the abstraction is so general (e.g. a root node of a tree of abstractions), then it's qualities will be reflected in every leaf node. Leaf nodes such as snow, rock, water, trees or burrito.
Monads embody the concept of "chaining" functions. Every monad implements the chaining in a different way, but the user of the monad doesn't have to know how it's done. It's just "call this function on foo, take the result and pass it to the next" and so on. Plain old functions do it with plain old function composition (functions are monads!) but something like Maybe will return None if it gets a None, and only otherwise pass the data on. The Future monad will await completion then pass it on (async/await is a monad!), the List monad will merge together zero or more list results from mapping over each of its items, and so on.
Again, the cool thing is that it's the same syntax no matter what kind of Monad you're in, so you can totally change the behavior of a monadic function by just using it with a more specific return type (at least in Haskell, other languages might make you pass around an implementation)
Monads are a neat tool of composition, but composing monads themselves is fiddly and cumbersome, involving monad transformer "stacks" of deeply nested generic types. Monad transformers are really just not fun.
I'd give specific examples, but I'm kind of lethargic from the burrito I had for lunch. If you're familiar with flatMap(), a Monad is anything that's "flatmappable" (which in JS is just arrays, but languages like Scala take it much further). In C#, it's IQueryable (LINQ is a monad!)
"Monads embody the concept of "chaining" functions."
No, it doesn't. Monad implementations may call the provided function zero times, or multiple times. Chaining implies exactly once, one of the common misconceptions about the monad interface.
Flatmap is another common attractive nuisance; flatmap is the monad implementation for lists but is not "monad" in general, any more than "an iterator on linked lists" is "iterator" in general.
Chaining implies sticking one function to another, with the implication that some data is being threaded through them.
Whatever extra semantics go on top of that are gravy, it's a vague term for a vague concept. I even gave examples where the function might be invoked zero times (Maybe) or multiple times (List). And in some languages (I specifically mention one) flatmap is a function on all monads, not just lists.
If you understand it correctly, then use correct terms to describe it. And I don't mean the mathematically correct pristine words that some particular FP language uses, I mean, terms that will create correct impressions in the audience and not incorrect ones. You don't help by using the most commonly misunderstood terms and reinforcing the widespread misconceptions about how it works. Both "flatmap" and "chaining" create completely wrong impressions. You just leave people having to clear out the misunderstandings to get to the real and not really that complicated truth.
Would it then follow that commands/builtins like `time` and `sudo` are monads? What about shell commands in general, since you can always chain together stdout to stdin with pipes?
Streams are definitely monads, so if you're reading lines from stdin and printing zero or more lines for each to stdout, you basically have a List monad (Haskell's laziness makes every list a poor man's stream, though if you're doing serious streaming work, you'd probably want something more specialized).
Mind you, shell pipes aren't actually implemented monadically, but you can certainly visualize it that way when working with functional libraries.
There's no point as such. They are a natural (non-leaky) generalisation of a recurring pattern in mathematics and software over which you can build some general theory and, in the case of programming languages such as Haskell, a general purpose library.
Haskell is one of the few languages that lets you write those general purpose libraries. In other languages, there's really no point talking about monads.
A "monad" is a description of something so general it covers lists, things that might not exist, things that might fail, and things that might happen in the future.
It is a concept from category theory, which is the branch of mathematics that deals with saying as little as possible about as much as possible.
You don't have to worry about any of this unless a teacher tells you to. (And if they do, you can just leave.)
It's an abstraction - a very high level abstraction.
Like all abstractions, if you squint hard enough, different things can look the same. Monads can make optionals, lists, error handling, i/o, continuations, state manipulation, reading values, writing values and many other things look the same.
They are interesting in purely functional languages like Haskell because they allow for side effects without sacficicing purity and referential transparency. Rather than allowing side effects, you describe the program as, say, specs for the instruction to run, plus the function that decides what the next instruction is after the first instruction completes and yields a result. Kind of like a linked list of functions that return imperative instructions.
I don't think the general concept is that interesting, at least in programming. Monoids, though simpler, are probably way more interesting (mapreduce, caching, etc)
Monads are Monoids, so it's a good idea to build up an intuition for Monoids first. Then move up to Applicatives, and then finally Monads if you find you actually need them by then. By the time you get to them, Monads will be boring.
Applicatives were dusty theory when I learned Monads, so they never burned themselves into my consciousness. I'm doing good with Monoids tho.
I dunno, it helped monads "click" for me at a theoretical level (they're just monoids in the category of endofunctors, what's the problem?) but by then I'd acquired a strong intuition of them already, so maybe it's not helpful for a novice. I'm pretty sure Applicatives help tho, LYAH seems to think so anyway. They sure would have helped me: the "Gentle Introduction to Haskell" was anything but.
Monads being monoids isn't very interesting in a practical sense - e.g. just because you can associatively combine steps in a program isn't very interesting. (We do it all the time when we extract a subset of instructions as a procedure)
I'd say monoids are more interesting, mainly due to implications for massively parallel algorithms and caching and how finding operations that fit the laws let you reap massive benefits in both aspects.
I've yet to see something as practically interesting for monads. Perhaps monadic parsers or STM qualify. What would you say makes monads just as interesting in a practical sense?
The easiest examples from mainstream languages are things like Result<T>, Future<T>, or Option<T>. Its honestly a very broad and vague way to say "some data with context". The advantage is that instead of using things like exceptions or panic/crash, your code must handle every case in order to get at the value unlike the normal case.
For example, an Option<T> could force you to unwrap the value and deal with errors. In true functional languages and/or where pattern matching is used, you must handle both cases, presumably creating a chain of "monads" all the way down (as most of your functions will return Option<T> or Result<T, Err> type patterns, since you can't throw an exception, and any value might be empty/error, so you have to propagate it upwards).
Honestly its a fancy term that functional programmers use to cow the laity. Its a bit like calling an if statement "probative procedural data query with indeterminate flow control."
I think a good write-up that shows some practical use cases of monads is a paper called "Monads for functional programming" by Philip Wadler. It assumes you're familiar with the basic syntax of Haskell or its predecessors, though.
Imagine eating a burrito bite by bite as you're performing a pure computation in your head. This would be captured as `Burrito Result` monad, where `Result` is the value that you're computing, and `Burrito` is the partially consumed burrito at the end of the computation. By representing it as a monad, you can pass said partially consumed burrito to someone else for them to compute while continuing to eat it, or you can delegate your computation to other people, passing burrito from one to the other. ~
A "side effect" is generally the behaviour that can be modeled by a Monad under chaining (join), and is not necessarily IO (although IO is a Monad). For a burrito, the side effect of join is the union of burrito contents.
Lambdas have two hidden features: Variable capture lets you access variables/values from outer scopes, and statements/calling other functions lets you do control flow.
Monads are types that let you build chains of lambdas, so you can mix control flow and variable scoping with the some type-specific logic.
Async IO/streams/optionals are monads. It's no accident that these could be programmed as nested callbacks/nested loops/nested if statements. That's the nested chain of Monad->lambda->Monad->...
Monads work well with syntax sugar (think async/await), and some languages like Haskell can abstract over them in neat ways.
Assuming you're familiar with basic programming concepts, it is practically speaking just the abstract definition of a way to ingest and pass around data in an unfamiliar context. Promises are a great example (packaging data for async processing), or the "Optional" wrapper on JVM return signatures (Passing around an object for possible-null handlers). Both those implementations fail on some purity grounds, but they're good for understanding the gist of your question, i.e. "the point" of one. (Even though I'm 73% sure some Haskell-ite is going to respond to me with an _ACTUALLY..._)
For me, everything made sense when I read that generator functions in python can be used to implement algebraic effects, and algebraic effects and monads express the same ideas/logic using somewhat different language (and for some, including me, the algebraic effect lagngauge is easier). So I like an explanation that goes like "langaues without either but with exceptions and async io > generators/coroutines > algebraic effects > monads".
On a fundamental level, all these describe abstraction over what happens "between functions calls" and how values are passed around.
1. Some languages provide special syntax for exceptions and async io - in both cases "something" might happen between function calls - either an exception handler is called under certain constitutions (eg prints logs) or a kernel level callback is created and the program is suspended.
2. One generalization of this can be implemented using python generator functions / coroutines - you can build a call graph by invoking all functions in you callstack as "yield from func()", and use "yield Exception" to propagate errors and "yield Timer(1)" to ask an outer caller (event loop) to wait, or "yield Print(mag)" if we want to keep our functions pure and make the C event loop handle all the IO. You can also pass values down the stack via corp.send(x).
3. But it is a little clunky. Some langaues like Koka give you an ability to define arbitrary "effects and effect handlers" which is essential special syntax for how we were abusing coroutines in the previous step that makes differentiating and handling different "kinds things that we pass up and down the callstack" (exceptions vs timers vs prints) easier.
4. But some langaues do not have effect handlers but still want to do "custom arbitary custom things between function calls". That's where monads come in - they define a type for defining chains/trees of function calls and rules for how these threes must be iterativley unwrapped and wrapped back depending on how the wrap looks like. Eg instead of doing a(b(c)) you say unit(c) | b | a and describe how piping must be done it terms of types of values that these steps process. I hope it makes it clear how one could implement side effect IO, or exceptions, or async io that pauses by defining how to "unwap such piping". In principle, you abstract away control flow by introducing your own syntax for building function call trees and then rules for writing piping that works with such trees.
That's actually the point of the article. There used to be a cottage industry in using real-world analogies to explain Monads. I'm not sure whether it started as an in-joke or not, but the most outlandish metaphor among others involved burritos (other metaphors out there that really did exist: assembly lines, toxic waste handling, and space suits). So now burritos are an in-joke among Haskell programmers, sort of a whimsical shibboleth.
Check my other post in this thread for what I hope is a more accessible explanation of Monads. They're a very abstract thing, so there's no concrete metaphors to be had, just a common thread of piping values through "contextful" functions.
Functional programming is dead. It never really caught on. For all the articles I saw from 2010 - 2020, functional languages are still as niche as ever. Influential languages like scala have their best functional features pulled into java. LLMs will further accelerate the decline. All the highest quality, most crucial software I have seen is written in something like Java, using classical OOP design patterns.
On the contrary, functional programming is mainstream. Even C++ and Java have had lambdas for many years now, higher-order functions are a common API pattern in all mainstream languages, C# and Python have pattern matching these days. Rust has ADTs even.
Pure functional programming didn't catch on, but most FP was never pure.
My response was to the "FP is dead" comment. It's not dead.
IMO React was a lot simpler to reason about than any of the competing frameworks at the time. It's a function that takes data and outputs a UI. If the data changes, the function is re-run.
How did React beat EmberJS (LinkedIn) and Angular (Google)? Marketing budget may be a factor, but "because billion dollar corporation" is not a conclusion I would draw.
Seems to be taking off (difficult to predict the long-term, of course), but Rust is excellent at functional paradigms and Brings many advancements of functional languages to the people.
Calling them 'pure' may be incorrect depending on how high your standards are.
Rust functions (which don't take mutable references) are incapable of modifying anything you pass into them, and any sort of global state is heavily discouraged.
This is pretty good as far as "pure" goes, but of course, they can perform I/O. (Then again, without any side effects a program would do nothing at all behind heating up your CPU, so this seems like a fair trade-off.)
What makes Rust functional are the patterns you can use in the language, though. A lot ot of maps, a lot of filters, a lot of iterators and algebgaic data types and pattern matching.
it's getting built into some pretty widepread applications: firefox is a big one, it also runs a lot of the super-low-level parts of dropbox. I'd say it's overtaken haskell for adoption at least.
I think that's the issue? Haskell was designed by and for geniuses (Larry Wall). But Python... certainly Guido likes compliments but for the most part he was an average programmer, designing for other average programmers. And now Python is #1 while Haskell is only #28. Like if you just skipped the "monad" terminology and called them "promises" or "futures" everybody would be less confused.
I don't think Guido is "an average programmer". But Python is a distant descendant of the ABC programming language (https://homepages.cwi.nl/~steven/abc/programmers/handbook.ht...), which, as the name implies, was meant as a better alternative to BASIC and Pascal for teaching.
[1] https://byorgey.wordpress.com/2009/01/12/abstraction-intuiti...