I think this is the mistake a lot of people make: they form an intuition of monads after seeing lots of examples of them, and then they try to communicate that intuition without just saying "here are lots of examples of monads".
That could be it. Perhaps my lack of understanding of category theory means I've only developed the pattern recognition for monads but not a deep understanding. So I can only explain it as the abstract thing all of these things have.
I am gonna be heavily opinionated, take my words with a bit of salt ;)
Here it goes:
1. One understood monads when one is able to write a monadic interface that behaves like any intermediate haskell would expect it to behave. This is not a hard task.
2. Screw the abstract stuff. While fascinating, really few people understand concepts going from abstract to concrete rather than the other way around. And don't get me wrong, I don't think these kind of people are better theorists or problem solvers, they just have to seem a special relationship with symbols. If you are one of those people, you would likely know - in any case, I would encourage to write 3-5 examples of monads in Haskell or Java or JavaScript or whatever language suits you and go from there.
I think it's because it's a concept that constrains how multiple instances of a type interact, and we're used to thinking of types as putting restrictions on single instances.
I think one problem is that there is the mathematical concept of monad and the programming pattern monad. On abstract level they are the same, but in practice and for illustrative purposes they are not.
There's a great FP podcast, the lambdacast. They aren't making new episodes, but if you listen in order, by the time you get to the monad episode it all makes sense n
Effects are encoded using a polymorphic type with a `map` operation (called a functor), and effectful computations are functions `a -> E b` for some types `a, b` and effect `E`.
A monad is exactly the plumbing you need for composing effectful computations: it turns a computation `b -> E c` into a `E b -> E c` so that you can compose `a -> E b` with `E b -> E c` to get a composed computation `a -> E c`.
It does so in a way that makes effectful composition associative (i.e. the way you'd expect)
Is Maybe/Option an effectful computation? Is List? I don't think it's really accurate to say this is what monads are "for". It's one use for monads but monads aren't "for" anything and there are monads that are not about effects.
"Effect" is perhaps a broader notion than one might think: it's an effect to write to a file or mutate some variable, sure, but it's also an effect to raise an exception, goto a label, raise an exception, execute asynchronously, be one of a set of possible outcomes, and plenty of other notions.
There's a name for the monad in which there is no effect: the identity monad.
The heart of a monad is the bind/flatMap/and_then interface, in which the value contained in the monad is computed on to produce a new monadic value. This is where the effect occurs: something other than just running the function happens.
If the bind implementation only runs the provided function then there's no effect, and it's the identity monad.
Operations on a list (like map, filter) are not effectful computations.
The problem with many monad explanations is they explain only particular monads. IO and State can be thought of as effectful computations, but List operations cannot.
Monads are just a pattern for composing computations, it doesn't say anything about the semantics of those computations.
In a typical imperative programming language, there's an "environment" that the actual lines of your program operate within. You can think of the environment as all of the sources your program can consume data from (files, databases, system clock, random number generator, keyboard, etc) and all of the sinks your program can write data into (files, databases, logs, the screen, etc).
Monads are an approach to modeling the idea of running a set of statements in a particular environment, with specific input and/or output capabilities, often called "effects".
In your typical programming language, you've only got one type of monad, which is the language runtime. You can do literally any type of effect at any time. That is very powerful, but it also makes programs difficult to reason about.
In many typed functional programming languages, monads are used to create much more specific contexts for running sequences of computations and effects. That makes reasoning about local sections of your program way easier.
One might think "why would I want to limit my power?" And the answer is you're only limiting yourself locally. It's exactly the same as how procedural programming dispensed with the all powerful GOTO, but it turned out that breaking programs into functions made them much easier to reason about.
And it turns out that once you start thinking that way, you can often offload some pretty tricky logic into a well designed monad.
A major caveat is that things can get pretty complex when you try to compose monads, and for me, that's where the love affair ended. There are alternative models for how to constrain effects, like algebraic effect systems, which try to be more intuitive and composable.
This describes some monads but not others. IO and State can be thought of as environments supporting effects, but it doesn't really make sense to think of a List like that.
The problem with many monad explanations is they only explain particular monads but then other monads become even more confusing.
I've heard the List monad described as modeling nondeterminism as an effect. As you chain flatMaps, at each stage, you're consuming a discrete set of possible inputs and producing a new set of potential outputs per input.
So it's a little different from thinking of your program as something that is once. Instead, you have your program being run multiple times, with the environment injecting different at each stage.
I think the Future/Promise monad is similar. The environment is taking responsibility for pausing and resuming computation.
But I'm curious if you have other monads in mind where the analogy fails.
A ordinary list expression like [x + 2 | x <- [7, 9, 13]] is not effectful or non-deterministic. It is completely deterministic and have no side effects.
Correct that there's no literal effect, but conceptually, if you wanted to model a particular sort of nondeterminism as a monad, you could use one indistinguishable from List.
And I think it holds just fine to think of the monad as injecting data into a sequential computation before each step and ingesting output after each step (boxed in the monadic container).
Which reminds me, that boxing is also part of the power of monads. It means you can nest steps of a monadic computation, instead of just chaining them, as a functor allows. This stimulates lexical scope -- bindings that exist for all statements after the one that defines them.
To me, it is because the term 'a monad' refers to a thing that doesn't even exist as its own thing in programming. Of course your can realize a monad, but the monad is not the type, nor the function, but the abstract, mathematical "thing" that defines a type and the two operations (value wrapping, and the compose function).
Programmers without a mathematical background will naturally struggle with such concepts. You can't point to where in the the code "the monad" is, as it's just the pattern, or the abstract mathematical combination of the type and the operations. Sure you could pack them into a class or a module, but it's still hard to point to where "the monad" is.
When we describe other patterns, like a Singleton, Factory, an Adapter, etc., it's is much more clear what and where the thing is. It's just a word to describe a function, a class, or a module.
Perhaps 'function composition with metainformation' would be a better title for the pattern. You take composable functions that act on certain types, and extend them to support metainformation attached to the values, while defining how the metainformation combines.
Yeah, I haven't done any Haskell but from what I understand "a monad" is just a type that lets you call `.map()` on it and do the normal thing that `map()` does. Would it be wrong to call it `mappable`? That would be a much clearer name.
It's not that. The thing you're describing is often called a "functor". It could be called a "mappable" but generally once you've heard a functor is a thing that has a .map() function that works the way you expect it to then you don't really need to call it a mappable anymore. So the name sticks.
Even to just be able to rapidly identify why having a thing that .map()s is a meaningful object represents the sort of experience with and abstraction of these concepts that'll put you ahead of the game for understanding monads.
Oh yeah I mixed them up. I still think `Mappable` is a much better name than `Functor`, and names matter. Especially for difficult to understand concepts. Functor isn't really even the right type of word. It should be Functable or something.
Monad should be `FlatMappable`. I found this great article where they use exactly these terms to explain it:
"Functor" is a perfectly good and descriptive name… provided you are at least somewhat familiar with the esoteric branch of mathematics it's based on. For someone from a Category Theory background renaming it to "Mappable" would be weird and unintuitive. And as in most things, those actually doing the work get to pick the names that make sense to them.
If you're not even slightly familiar with that branch of mathematics it's still a perfectly good and descriptive name, because you can search for the term and get loads of relevant information from the programming side of things.
(The mathematical basis helping you get deeper insights into it is just a bonus, albeit IMO a valuable one.)
Names certainly do matter: they're how we effectively communicate with each other. If you call it a mappable, and someone else calls it a chainable (as was said elsewhere in this comment thread), how have we eased confusion? If I try to google what a chainable is, what will I find?
On the other hand, if I say "it's a monad" then people who have encountered the term before know what I mean (even if they've had no need to understand monads in depth) and people who have not will successfully find information about the right thing if they search for it.
Monads have a particularly bad reputation for being hard to learn, despite appearing in non-general form in every modern language. Since people seem to have little trouble grasping the way in which futures, streams, and options work I don't think there's really such a great challenge involved - but the culture of running screaming from the mere word doesn't help matters in the least.
I'm not saying functor is a good name. I'm saying it's the name that's more or less stuck. To start using a new one is just to make the terminology even more confusing.
For what it's worth, "functor" properly describes the combination of a type and its relevant .map() function. It's (common) shorthand to refer to just the type as "a functor" when there's a unique, well-known functor that utilizes that type.
So, we could be more precise, of course. But also if you just throw up your hands when someone says "a list is a functor" then you're fighting a difficult battle.
What you are describing is a Functor, one the two other important Haskell "design-patterns" (Functor - Applicative - Monad). "Mappable" is a good name insofar as it is more familiar to new folks. However, I like the mathematical name as it forces one to embrace the interface as what it is, as opposed to thinking about Functors as collection of data that can be mapped over. Often this is true, but not always.
A monad could be described as "sequencable/chainable", but that metaphor is quickly breaking down as there as so many ways in which monads show up.
> one the two other important Haskell "design-patterns" (Functor - Applicative - Monad)
These days you should probably add Foldable and Traversable to that list, as they are closely related and appear throughout the standard libraries and even in the Prelude.
Traversable is a generalization of Functor to include Applicative effects. It reduces to Functor if the Applicative happens to be the Identity type.
Where Functor and Traversable replace values one-to-one while preserving structure, Foldable represents reducing operations (folds) which consume the values from a structure in some defined order to build up a result. This includes basic functions like sums and products, as well as more complex operations like `sequence`.
> A monad could be described as "sequencable/chainable", but that metaphor is quickly breaking down as there as so many ways in which monads show up.
Hmm well it might break down but simply by choosing a better word you've helped me understand what a monad is more than any article I've read on the matter.
This is a pretty good explanation but it's missing one detail: the laws.
A monad is an applicative functor which is a functor. Each of these concepts in the hierarchy comes with a set of laws which must be obeyed. For example, all functors (which define an operation, fmap) must obey 2 laws:
Identity
fmap id == id
Composition
fmap (f . g) == fmap f . fmap g
All instances of Functor must obey these laws. Furthermore, since every Monad is also a Functor, all instances of Monad also obey these laws (in addition to extra laws).
Imagine if functions were explained the same way: A function is just a mapping from a domain to a codomain. This would also be pretty useless and confusing for someone trying to lean how to write print("hello world")
For 99% of people, monads are best understood by a) using and writing many monadic interfaces and b) forgetting about the whole mathematical background.
What a monad offers is: "1: Do A. After A is done, you can inspect the result of A and do either B or C. 2: You can chain monads of the same type together.". Sounds easy? It is because it is!
The big fuzz about Monads is mainly because prior to monads, doing IO was akward in Haskell. Monads offered a convenient way to do IO (and other interactions with the outside worlds, such as databases, networking, ...) and turn up basically everywhere. There is nothing stopping one from introducing a monadic interface in OO-World, and it is partly done (i.e. "orElse" or "then") in some domains. However, bigger gain is to be expected in Haskell, since the type checking system forces the developer to deal with monads in a disciplined way.
-----
I admit being guilty of having only the title prior to writing the comment. But even with only having read just the word 'monad' in the title together with 'lazy', I knew the author would describe 1) some way of lazily evaluating one thing after another and 2) some way of combining multiple lazily evaluated things with each other. After skimming, this is what the article is about. As always, the interesting stuff is not about the concept of a monad (which is pretty easy), but about how to model/code 1) and 2) in a specific context (in this instance, being lazy evaluation).
I found Brian Beckman's video Don't Fear the Monad was extremely helpful for understanding monads as a practical user. I now use them everywhere. I don't know the first thing about category theory.
Monads in OO-world often fall flat because monads usually add something to a computation (ability to store state, ability to throw exceptions, ability to do I/O, ability to return `null`, etc) and in Haskell the basic building block is the pure function which can do none of those things. But in (say) Ruby, every method I write has already been given rights to return nil, write to the logging system, delete the database or launch the missiles no matter from where it is called in the program. The ErrorT monad transformer is much less useful when exceptions are a key part of the language and intended to be used everywhere.
There is a famous quote from a mathematician (maybe von Neumann, don't have it to hand) to the effect that you don't understand mathematics, you just get used to it. That's how monads work. You won't figure them out from thinking through the definition or listening to other people's explanations, but you will start to feel comfortable with them if you use them enough.
A mathematical analogy from introductory analysis is the concept of a dense set. If you're encountering the concept for the first time, it's easy to understand the definition, but it's hard to see why it matters. Then you start using it to solve problems and write proofs, and it's incredibly useful. After a while, you get a feeling about it, an intuition about when it might be helpful, and a facility at working through the technical details of using it. You get used to its power, in a way that feels like understanding. But you don't learn anything that can be passed on in words or symbols. You can't say anything to a beginner to clear up their confusion. You can only point them towards the exercises through which you acquired your understanding. Monads work exactly the same way.
As I like to say, design patterns for strongly-typed functional programming languages is essentially category theory. What matters, from a practical point of view, is that you know how to effectively use such patterns when decomposing systems to smaller parts (aka, functions) and composing them (monads et. al.).
Monads as a concept in category theory are hard to understand without a bit of background. However, monads as a design pattern are not much more difficult than singletons, strategy pattern, visitors etc. I think the reason many monad tutorials fail is that the authors have an interest in category theory, so they find it hard to target the level of their audience.
Here's my attempt for Java programmers.
Suppose that:
- Values (like `String` and `int`) can be put into "boxes" (like `Async<String>`)
- We can call the box a "monad" when we have a few static methods for operating on the boxes that are well behaved:
The implementation of these two methods depends on the box we are talking about.
You may see these under different names, which makes things extra confusing:
1. "just", "from", "pure", "return"
2. "flatMap", "bind"
OK, but why would you want to do this?
Well, if you are doing a complex procedure that manipulates these boxes, then having these methods allows us to build that complex procedure out of simpler parts. It's highly compositional.
Additionally, the monad structure allows languages (not Java sadly) to give you really powerful syntactic sugar that avoids nesting. In Haskell this is called do-notation; F# has Computation Expressions. Many more traditional languages have a less general version in the form of async / await.
Where this can directly been seen in Java is the Optional class which has the methods you describe as instance methods. Though Java has no syntactic sugar for monads this allows chaining expressions (which I think in Haskell would be bind() chains). Java streams behave similarly (and form a kind of monad around Collections).
Having monad (or functor) functions (flatMap, map,...) as static functions has the disadvantage that you can't chain them well.
Java solves this with map as instance function (transfering the function inside the box). And there are several extraction functions (See Optional for an example).
Your 3a is certainly necessary in order for each individual monad to be useful, but it is not general (i.e it depends on the monad how you do it). Therefore, it's not part of the criteria of what it means to be a monad.
Examples:
- Async: Block and wait for your Async<T> to finish or fail. (Note that Async<T> can still be useful without that if you can simply register a final continuation callback with side effects)
- Option: Check if there is a value and unwrap it if that's the case. Or, maybe more idiomatic: Some way to write a `match` statement ("if there's a value, do A. Otherwise do B. Both have to return the same result type")
- List: Provide ways to check the length and access each individual value in the list.
- Stream: Provide a way to wait for the next value.
…and so on and so forth.
Your 3b is implementable in terms of `bind` and `return` (and, in case your monad is also a functor, even in terms of `map`).
Edit: To drive the point about 3a home a bit more:
The reason why monads (and functors) as a pattern are interesting is that they provide the formalistic means to write "imperative" code by writing pure functional "in between" code:
Do A
…and then do B
…and then do C
Different monads essentially only influence what "and then" means. A lot of languages have syntactic sugar for this kind of "and then" lists, for example:
Haskell: do notation
F#: Computation expressions (e.g. `async` or `seq` blocks)
Python, C#, Javascript: Async methods and functions
C#: Inline SQL with LINQ
Rust: `try` and `async` blocks
You'll note that each of these notations return the final monad instance without unwrapping it. That's because "unwrapping" is either not general enough for the notation or not always desirable. I.e. an `async` method in C# returns a `Task<T>`, you'll have to block for the result yourself.
So, the "Monad" pattern is only concerned with how you can compose operations, not with how you can get to the resulting value.
In general you can't take the value from the box: you can't make an async computation non-async; you can't make a non-deterministic function deterministic; you can't make a possibly-failed result successful.
The await keyword might make it look like you can escape the box - but in effect a statement like "let x = await y" is just expanding the async box around y to also be around the bock containing that statement.
Since we already have a function to take a plain value to one in a box ((1) above) and a way to apply a function that turns a value from a box into another boxed value, lifting is just function composition:
Since everyone is just writing their own explanation of monads, here is mine:
Monads are a pattern for function chaining. It can be compared with fluent interfaces in OOP, which is also a pattern for chaining operations. A fluent interface does not imply any particular semantics - the semantics of a fluent chain will depend on the specific object it is used on. Same with monads, where each type which support the monad pattern provide its own unique semantic for how the operations are performed.
The name "monad" is misleading since it is a noun. Perhaps "Chainable" would be a better name?
The pattern can be used in any language which support higher-order functions, but without any syntax sugar support it tend to be rather verbose compared to say fluent interfaces. But with built-in syntax sugar support like in Haskell, monads becomes viable for many purposes.
Since JavaScript is the lingua franca of today, here is an example of a method chain in JavaScript:
Although in both cases it is clearly more verbose than the first non-monad method chain. But with some syntactic sugar with hide the repeated flatMap boilerplate, it can be made pretty concise.
59 comments
[ 3.3 ms ] story [ 128 ms ] threadI didn't understand it for years then realized I had seen it all over the place.
Now that I think I know what it is I can explain to someone that doesn't already know what it is.
Here it goes:
1. One understood monads when one is able to write a monadic interface that behaves like any intermediate haskell would expect it to behave. This is not a hard task.
2. Screw the abstract stuff. While fascinating, really few people understand concepts going from abstract to concrete rather than the other way around. And don't get me wrong, I don't think these kind of people are better theorists or problem solvers, they just have to seem a special relationship with symbols. If you are one of those people, you would likely know - in any case, I would encourage to write 3-5 examples of monads in Haskell or Java or JavaScript or whatever language suits you and go from there.
After trying to understand it at a more fundamental level I have a suspicion that that piggy wiggies are involved.https://www.google.com/amp/s/bartoszmilewski.com/2014/10/28/...
In short: Functors (a tightly related concept) are about types that have ‘.map()’, and Monads are Functors that in addition have ‘.flatMap()’.
Effects are encoded using a polymorphic type with a `map` operation (called a functor), and effectful computations are functions `a -> E b` for some types `a, b` and effect `E`.
A monad is exactly the plumbing you need for composing effectful computations: it turns a computation `b -> E c` into a `E b -> E c` so that you can compose `a -> E b` with `E b -> E c` to get a composed computation `a -> E c`.
It does so in a way that makes effectful composition associative (i.e. the way you'd expect)
Yes, Maybe/Options are a way of encoding partial functions/exceptions.
List is a way of encoding non-determinism.
There's a name for the monad in which there is no effect: the identity monad.
The heart of a monad is the bind/flatMap/and_then interface, in which the value contained in the monad is computed on to produce a new monadic value. This is where the effect occurs: something other than just running the function happens.
If the bind implementation only runs the provided function then there's no effect, and it's the identity monad.
The problem with many monad explanations is they explain only particular monads. IO and State can be thought of as effectful computations, but List operations cannot.
Monads are just a pattern for composing computations, it doesn't say anything about the semantics of those computations.
They can, the effect modeled by the list monad is non-determinism.
Monads are an approach to modeling the idea of running a set of statements in a particular environment, with specific input and/or output capabilities, often called "effects".
In your typical programming language, you've only got one type of monad, which is the language runtime. You can do literally any type of effect at any time. That is very powerful, but it also makes programs difficult to reason about.
In many typed functional programming languages, monads are used to create much more specific contexts for running sequences of computations and effects. That makes reasoning about local sections of your program way easier.
One might think "why would I want to limit my power?" And the answer is you're only limiting yourself locally. It's exactly the same as how procedural programming dispensed with the all powerful GOTO, but it turned out that breaking programs into functions made them much easier to reason about.
And it turns out that once you start thinking that way, you can often offload some pretty tricky logic into a well designed monad.
A major caveat is that things can get pretty complex when you try to compose monads, and for me, that's where the love affair ended. There are alternative models for how to constrain effects, like algebraic effect systems, which try to be more intuitive and composable.
The problem with many monad explanations is they only explain particular monads but then other monads become even more confusing.
So it's a little different from thinking of your program as something that is once. Instead, you have your program being run multiple times, with the environment injecting different at each stage.
I think the Future/Promise monad is similar. The environment is taking responsibility for pausing and resuming computation.
But I'm curious if you have other monads in mind where the analogy fails.
And I think it holds just fine to think of the monad as injecting data into a sequential computation before each step and ingesting output after each step (boxed in the monadic container).
Which reminds me, that boxing is also part of the power of monads. It means you can nest steps of a monadic computation, instead of just chaining them, as a functor allows. This stimulates lexical scope -- bindings that exist for all statements after the one that defines them.
Programmers without a mathematical background will naturally struggle with such concepts. You can't point to where in the the code "the monad" is, as it's just the pattern, or the abstract mathematical combination of the type and the operations. Sure you could pack them into a class or a module, but it's still hard to point to where "the monad" is.
When we describe other patterns, like a Singleton, Factory, an Adapter, etc., it's is much more clear what and where the thing is. It's just a word to describe a function, a class, or a module.
Perhaps 'function composition with metainformation' would be a better title for the pattern. You take composable functions that act on certain types, and extend them to support metainformation attached to the values, while defining how the metainformation combines.
Even to just be able to rapidly identify why having a thing that .map()s is a meaningful object represents the sort of experience with and abstraction of these concepts that'll put you ahead of the game for understanding monads.
Monad should be `FlatMappable`. I found this great article where they use exactly these terms to explain it:
https://dev.to/joelnet/functional-javascript---functors-mona...
If people are using different terms to explain your confusing terminology then... maybe the confusing terminology is not very good.
(The mathematical basis helping you get deeper insights into it is just a bonus, albeit IMO a valuable one.)
On the other hand, if I say "it's a monad" then people who have encountered the term before know what I mean (even if they've had no need to understand monads in depth) and people who have not will successfully find information about the right thing if they search for it.
Monads have a particularly bad reputation for being hard to learn, despite appearing in non-general form in every modern language. Since people seem to have little trouble grasping the way in which futures, streams, and options work I don't think there's really such a great challenge involved - but the culture of running screaming from the mere word doesn't help matters in the least.
For what it's worth, "functor" properly describes the combination of a type and its relevant .map() function. It's (common) shorthand to refer to just the type as "a functor" when there's a unique, well-known functor that utilizes that type.
So, we could be more precise, of course. But also if you just throw up your hands when someone says "a list is a functor" then you're fighting a difficult battle.
A monad could be described as "sequencable/chainable", but that metaphor is quickly breaking down as there as so many ways in which monads show up.
These days you should probably add Foldable and Traversable to that list, as they are closely related and appear throughout the standard libraries and even in the Prelude.
Traversable is a generalization of Functor to include Applicative effects. It reduces to Functor if the Applicative happens to be the Identity type.
Where Functor and Traversable replace values one-to-one while preserving structure, Foldable represents reducing operations (folds) which consume the values from a structure in some defined order to build up a result. This includes basic functions like sums and products, as well as more complex operations like `sequence`.
Hmm well it might break down but simply by choosing a better word you've helped me understand what a monad is more than any article I've read on the matter.
A monad is an applicative functor which is a functor. Each of these concepts in the hierarchy comes with a set of laws which must be obeyed. For example, all functors (which define an operation, fmap) must obey 2 laws:
Identity
fmap id == id
Composition
fmap (f . g) == fmap f . fmap g
All instances of Functor must obey these laws. Furthermore, since every Monad is also a Functor, all instances of Monad also obey these laws (in addition to extra laws).
The main difference is that design patterns have 'fuzzy' rules whereas monads are more precisely defined.
https://two-wrongs.com/the-what-are-monads-fallacy
What a monad offers is: "1: Do A. After A is done, you can inspect the result of A and do either B or C. 2: You can chain monads of the same type together.". Sounds easy? It is because it is!
The big fuzz about Monads is mainly because prior to monads, doing IO was akward in Haskell. Monads offered a convenient way to do IO (and other interactions with the outside worlds, such as databases, networking, ...) and turn up basically everywhere. There is nothing stopping one from introducing a monadic interface in OO-World, and it is partly done (i.e. "orElse" or "then") in some domains. However, bigger gain is to be expected in Haskell, since the type checking system forces the developer to deal with monads in a disciplined way.
-----
I admit being guilty of having only the title prior to writing the comment. But even with only having read just the word 'monad' in the title together with 'lazy', I knew the author would describe 1) some way of lazily evaluating one thing after another and 2) some way of combining multiple lazily evaluated things with each other. After skimming, this is what the article is about. As always, the interesting stuff is not about the concept of a monad (which is pretty easy), but about how to model/code 1) and 2) in a specific context (in this instance, being lazy evaluation).
I found Brian Beckman's video Don't Fear the Monad was extremely helpful for understanding monads as a practical user. I now use them everywhere. I don't know the first thing about category theory.
A mathematical analogy from introductory analysis is the concept of a dense set. If you're encountering the concept for the first time, it's easy to understand the definition, but it's hard to see why it matters. Then you start using it to solve problems and write proofs, and it's incredibly useful. After a while, you get a feeling about it, an intuition about when it might be helpful, and a facility at working through the technical details of using it. You get used to its power, in a way that feels like understanding. But you don't learn anything that can be passed on in words or symbols. You can't say anything to a beginner to clear up their confusion. You can only point them towards the exercises through which you acquired your understanding. Monads work exactly the same way.
Here's my attempt for Java programmers.
Suppose that:
- Values (like `String` and `int`) can be put into "boxes" (like `Async<String>`)
- We can call the box a "monad" when we have a few static methods for operating on the boxes that are well behaved:
1. A method for constructing a box from a value:
2. A method for transforming a box into a new one with a possibly different value: The implementation of these two methods depends on the box we are talking about.You may see these under different names, which makes things extra confusing:
1. "just", "from", "pure", "return"
2. "flatMap", "bind"
OK, but why would you want to do this?
Well, if you are doing a complex procedure that manipulates these boxes, then having these methods allows us to build that complex procedure out of simpler parts. It's highly compositional.
Additionally, the monad structure allows languages (not Java sadly) to give you really powerful syntactic sugar that avoids nesting. In Haskell this is called do-notation; F# has Computation Expressions. Many more traditional languages have a less general version in the form of async / await.
Having monad (or functor) functions (flatMap, map,...) as static functions has the disadvantage that you can't chain them well.
3a. A method for extracting the value from the box
Or, alternatively, to be able to re-use the entire standard library for your boxed values (which is not possible in Java, FAFAIK):3b. Take a plain-valued function and lift it to accept boxed values:
Your 3a is certainly necessary in order for each individual monad to be useful, but it is not general (i.e it depends on the monad how you do it). Therefore, it's not part of the criteria of what it means to be a monad.
Examples:
- Async: Block and wait for your Async<T> to finish or fail. (Note that Async<T> can still be useful without that if you can simply register a final continuation callback with side effects)
- Option: Check if there is a value and unwrap it if that's the case. Or, maybe more idiomatic: Some way to write a `match` statement ("if there's a value, do A. Otherwise do B. Both have to return the same result type")
- List: Provide ways to check the length and access each individual value in the list.
- Stream: Provide a way to wait for the next value.
…and so on and so forth.
Your 3b is implementable in terms of `bind` and `return` (and, in case your monad is also a functor, even in terms of `map`).
Edit: To drive the point about 3a home a bit more:
The reason why monads (and functors) as a pattern are interesting is that they provide the formalistic means to write "imperative" code by writing pure functional "in between" code:
Different monads essentially only influence what "and then" means. A lot of languages have syntactic sugar for this kind of "and then" lists, for example:Haskell: do notation
F#: Computation expressions (e.g. `async` or `seq` blocks)
Python, C#, Javascript: Async methods and functions
C#: Inline SQL with LINQ
Rust: `try` and `async` blocks
You'll note that each of these notations return the final monad instance without unwrapping it. That's because "unwrapping" is either not general enough for the notation or not always desirable. I.e. an `async` method in C# returns a `Task<T>`, you'll have to block for the result yourself.
So, the "Monad" pattern is only concerned with how you can compose operations, not with how you can get to the resulting value.
The await keyword might make it look like you can escape the box - but in effect a statement like "let x = await y" is just expanding the async box around y to also be around the bock containing that statement.
Since we already have a function to take a plain value to one in a box ((1) above) and a way to apply a function that turns a value from a box into another boxed value, lifting is just function composition:
Monads are a pattern for function chaining. It can be compared with fluent interfaces in OOP, which is also a pattern for chaining operations. A fluent interface does not imply any particular semantics - the semantics of a fluent chain will depend on the specific object it is used on. Same with monads, where each type which support the monad pattern provide its own unique semantic for how the operations are performed.
The name "monad" is misleading since it is a noun. Perhaps "Chainable" would be a better name?
The pattern can be used in any language which support higher-order functions, but without any syntax sugar support it tend to be rather verbose compared to say fluent interfaces. But with built-in syntax sugar support like in Haskell, monads becomes viable for many purposes.
Since JavaScript is the lingua franca of today, here is an example of a method chain in JavaScript:
The same result using the monad pattern: The strength of the monad pattern is that it is more composable. For example the same result can be achieved by nesting rather then chaining: Although in both cases it is clearly more verbose than the first non-monad method chain. But with some syntactic sugar with hide the repeated flatMap boilerplate, it can be made pretty concise.Other type classes can be chained. Like Applicative.
Applicative is a chain where you cannot inspect the per-step state or whatever. But you can with Monad.