There seems to be a disproportionate amount of tutorials about monads. Not being able to provide sufficient amount of examples in other languages makes monads seem quite less useful. Maybe next article to write is “why should you learn about monads”.
"If you don't need IO sequencing, or purity because your language doesn't care about purity, or the list implementation's nondeterminism because that's a parlor trick, or STM because your ecosystem doesn't use it... there's not much point to worrying about it. Monad implementations in languages other than Haskell fail to take off because there generally isn't even a single thing that is improved by working with it through a monad interface, let alone a family of such things."
"[...]It's definitely an interesting idea, and if you are a curious programmer, it's worth learning about this particular interface and what it can do. But it's not something you generally directly take back to your language"Nonetheless, these are all monads, and learning specific monads helps in lots of other languages. Optionals are nice in Java. Arrays in Lisps. Error handling in Scala. But the "universality" of monads escapes these languages. I would kill for better monadic error handling in Go, but conversely I would have little use for monadic I/O or futures.
The critical mental step is seeing the commonality in all of these, which is really only possible in languages at that level of type sophistication. And necessary in Haskell where the particular combination of purity, laziness, and HKTs means monads are the only way decent way to do most of these. Once you see that commonality you can develop tools - patterns of mental reasoning about code and idioms for writing code - you can also use when dealing with the specific monads or nearly-monads in other languages. But you can't show that property in examples in a single other languages, and it's difficult to show how some deep structure is shared in examples spanning five languages.
If your type system can't express them then you lose most of the advantages and they're pretty pointless.
it's basically continuation-passing-style (`a -> m b` is the "continuation"), you might as well ask "why can you represent so many control-flow things using CPS?". idk why, but you can!
from another angle, you could compare Monad with the less powerful Applicative. a formulation[0] that's easier to parse than the usual one[1] is:
class Functor f => Applicative f where
unit :: f ()
pair :: f a -> f b -> f (a, b)
if you're familiar with JS Promises, a rough analogy would be >>= (Monad) Promise.then
pair (Applicative) ≈ Promise.all
unit (Applicative) =
return (Monad) = Promise.resolve
ignoring parallelism, you can implement Promise.all using Promise.then, but not the other way around.---
[0] Called "Monoidal" here: https://stackoverflow.com/q/45267953 [1] https://en.m.wikibooks.org/wiki/Haskell/Applicative_functors...
Why that type signature? You only have two options: that, or m (m a) -> m a. The former is more popular for ergonomic reasons mostly. It doesn’t really matter.
The definition of a monad, as it relates to programming, emerges when you realize that an Option type, a Result/Either type, a List type, and a bunch of other non-container types all have the equivalent of a `flat_map` function. Write out a few mathematical rules for how a well-behaved implementation should compose and you have a Monad and its rules.
This is why another approach to learning monads that isn't focused on the theory introduces it as a design pattern instead. I find this to be great for intuition but horrible for understanding personally, having used both approaches to teach people about Monads IRL.
Nevertheless language ecosystems outside Haskell, scala and F# don't recognise this as a general programming pattern, although given the current direction towards FP in the JavaScript and rust communities I think it's a matter of time.
At first glance monads seem less useful in languages with builtin mutable state, and freely allowing side effects. However, if you’re writing somewhat more high-level domain specific code there are plenty of concepts that can be modeled using functors and monads very elegantly.
Parser combinators, distributed data fetching, reactive data streams, composable tree traversals, etc
I use them all the time in for TypeScript.
I would really like to understand what they are, but so far have not been able to find any explanation which tries to explain them in terms of english, math or some commonly known programming language.
I can provide you a simple example of a function with monadic interface, you will understand it any language but that will not actually help you to conceptualize the idea of monads and influence your thinking.
You will just think that's simple, whats the fuss about it and miss the pattern altogether.
Imagine asking the user to provide a number, in most languages you will get a `string | null` value and you wish to get a real number from it by parsing the string if the user entered it.
In this case assuming you have a function `parseNo` that goes from `string -> int | null` you can create a special function which takes a `string | null` value along with the `parseNo` function and gives you back `int | null`. S
Turns out that function can be defined for many different data types and it has special useful properties.
In this case it allows us to compose functions neatly, without having to do a whole bunch of null checks.
But in other scenarios it can do a lot more.
You can provide a function with the type signature `a -> b`. With this we run the function underneath `m` and map `m a -> m b`.
Concretely this might be with for example lists, where given a function `Int -> String` I can map `[Int] -> [String]`. What the functor abstraction gives you is a consistent, lawful way to define this phenomenon for virtually every type you'd ever want to "map". Likewise monads for "flat mapping".
This explanation was 90% accurate. What's missing is that not every functor or monad is a "container", for example Haskell's `IO`, which rather represents an action of sorts to impurely get a value. But the intuition will build all the same if you play with it a bit. It's all in the type signatures.
This is about 90% of the way there (and TFA actually contains very similarly phrased description, right before the monad definition you complained about).
The problem is that English does not have the right words to describe the abstract idea behind "source", "way of" and "operation" precisely and rigorously.
Regarding "any common programming language": In a way, this is like trying to explain a concept like, say, "the adjoint operator" [0] to someone only aware of integers. Or trying to explain the idea of the Liskov Substitution Principle [1] to someone who just learned assembly.
In each case above, the language used to describe the concept is multiple layers of abstraction too low to succinctly ping down the concept in question. Haskell brings (or rather is) a very rich language suited specifically for accurately describing the elements involved. That's why 1) the concept of a Monad is visible/cleanly expressible in the first place, and 2) people use it to explain the concept, even when (like this blog post) not aiming at a Haskell (or Haskell learner) audience.
Disclaimer: I'm just a lowly OOP programmer, not written a line of Lisp or Haskell in my life. Watching from the sidelines for now.
[0]: https://en.wikipedia.org/wiki/Hermitian_adjoint
[1]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
Did you mean to write "Say you have a source of A, and a way of turning an A into a B. An operation that uses these two inputs to produce a source of B is a monad"?
What exactly do you consider a "source"? And the way of turning an A into a B I would call a function. And combining the "source of A" with a function that converts A's into B's, a concationation or composition of functions, or a way of currying.
And yes, I am fully aware, that you sometimes need to learn the vocabulary to have a minimum discussion. But considering I have a degree in math and CS, have learned at least 10 programming languages, especially Lisp with all of its contexts, I am highly suspicious about something that cannot expressed in plain language. But thanks, your explanation comes closest I have seen so far.
No. That would be the Functor concept explained in TFA. A Monad is specifically what I wrote.
> And combining the "source of A" with a function that converts A's into B's, a concationation or composition of functions, or a way of currying.
Yes, pretty much. See the article (where one of the main points is specifically that the functor concept is not a highly complex thing. Just a name for a kind of thing - an abstraction).
> What exactly do you consider a "source"?
That's exactly the point where the "genericness" of the concept (consider: it originates from category theory) precludes discussing it in more specificity. It could be a Maybe<A> (std::optional in C++), it could be a List<A>, it could be a PotentialFutureUserInput<A> (aka IO in Haskell), it could be a (C#) Enumerable... Anything that "wraps" (in any sense) another type. A functor allows you to apply a function that transforms the inner type into another without leaving the wrapper. A monad allows you to apply a function to the inner type, transforming it into a different now wrapped type, with the monad implementation taking care of "flattening" the wrappers. I will avoid the attempt to come up with an example, seeing as the article also criticizes the abundance of bad monad examples.
Here, I've written a C++20 concepts version of Monad. Well, I hope I did. But even if correct, it's just not something C++ can express well, much less use well: Since functions are not first-class citizens, you can't spell "a function from A to M<B>" in a way the compiler can deduce. That's why you need the extra template argument F and corresponding constraint FAMB.
So something can behave similarly to a monad, but the type systems don't stop you self-sabotaging your code or misunderstanding good composition.
Best way to learn is to learn Haskell deliberately. Better than Go, the language forces you to compose better code, and even learn the values of FP.
If one is uninterested in learning CS or expanding one's toolbelt, it'll take some years until industry catches up.
> tries to explain them in terms of english, math
There are a million English-language blog posts, and it’s a mathematical concept. Just look up “monads are monoids in the category of endofunctors” if you want more of that…
To put that a bit differently: Haskell is a strait-jacket. In the name of purity and laziness, it takes away a lot of things that other languages give you. Monads are the hoop that is left, which you have to jump through in order to be able to do what you can do in a more straightforward way in other languages.
I'm saying this in a negative way precisely because you said it in a positive way.
But which is it really? Is it a positive or a negative? Depends on who you are and what you're doing. Depends on how much your program is improved by not having the wiggle room, and how much of the time you're doing what is easier to do in other languages.
("Easier" might not be the right word. Do notation isn't that bad of a notation for expressing sequencing in. It's a bit cumbersome, but it's not terrible - it's not like building the sequence monads by hand. It's still producing them under the hood, though. Whether that's a problem depends on how badly you need speed, how much pressure the garbage collector is under, how much of the program needs to be sequential, and so on.)
It probably won’t be very helpful. The definition of a monad is 90% in the types.
People use Haskell to talk about monads for two reasons:
1. Its type system is expressive enough to represent them (parametric polymorphism and higher-kinded types)
2. It’s one of a very small number of languages that doesn’t let users wiggle their way around needing some kind of principled effect system, so you really can’t get away without monads. Only in such an environment can the social pressure to take shortcuts be overcome, forcing people to structure their code carefully enough that the rich concept of monads isn’t reduced to a few occasionally used and probably incorrectly implemented corner cases like optional values and maybe futures.