Monads are just a monoid in the category of endofunctors. If you dont understand that, skill issue
Tap for spoiler
/s
God, you guys are idiots, it’s so simple
- You take a Function
- ???
- Result It can’t be simpler /s
No joke that’s pretty much every example I came across trying to get my head around it :D.
Not sure if using analogies is helpful or just going to be more confusing but, the way I think of a monad is similar to how I used to cook back when I worked in restaurants. I’d prep all my ingredients in small containers so I wouldn’t forget anything, and they’d be ready to go when needed. Then I’d start adding them to the main mixing bowl, one step at a time. If I forgot an ingredient or accidentally flipped the bowl, the recipe would fail — you can’t keep baking after that.
So a monad is like that bowl: if you mess up, it just dumps everything out and resets your little prep bowls, instead of letting you keep going and make a batch of shitty cookies
The “main-bowl” is the monad (the context that holds your values).
The “prep bowls” are the individual values or functions ready to be chained.
The “dump/reset” is the idea that once something goes wrong, the chain stops safely.
And “shitty cookies” are the result of not putting a monad in place and just sending it.
Maybe someone with a more diverse programming background can explain it better. But it’s basically a function checker usually wraped in IF ELSE and RETURN.
Some pseudo code in case my analogy doesn’t make sense.
def main(): bowl = get_flour() bowl = add_butter(bowl) if bowl is None: return "Recipe failed — restart!" bowl = add_sugar(bowl) if bowl is None: return "Recipe failed — restart!" return bake(bowl)
Isn’t your example just the builder pattern?
Yeah, that explanation is missing the critical point of generically applying external functions through
flat_map
/bind
I think this is a good explanation: https://fsharpforfunandprofit.com/rop/
A monad is a builder that lets you use previous partial results to make decisions while you build.
I like your explanation, that makes a lot of sense
Do you know C#? LINQ? IEnumerable<T>? IEnumerable<T> is a monad. That’s how LINQ works.
You’ve been using monads all along.
Or for those using Java: Stream<T> is a monad
Optional is my favorite example to give, at this point most people have internalized how to use its map function and how it works
That’s a good one.
A rule of thumb is that if it has map and flatMap (or equivalent), then chances are that it’s a monad.
Seriously, can someone please explain monads like we’re dumb or something ?
I’m not good at this but that’s never stopped me from making a fool of myself before.
Iterators are monads because they have a
flatMap
on them. It takes each element and spits out a new iterator which is merged in to the result.Option is a monad too. Same reason. You can map the contents to another option. And you won’t get called if there’s nothing inside.
Promises are monads too. You can map the result to another promise. The wrinkle here is that you don’t get to know when the map happen. Or it might not get called at all if the promise errors out.
IO can be a monad because you can ask it for input and wait for the result. It’s just the same as a promise.
See how these different things share a common behavior? That’s monad. Or, maybe it’s monoid. Names are hard and I’m busy making a fool of myself.
Monads are nothing more than a useful abstraction. Haskell is famous for them because they couldn’t make Haskell do imperative stuff without them so they spread them all over the language.
We all use them every day in regular programming. We just don’t think of them as a class of thing.
This is probably the best explanation I’ve seen:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
I’ll assume you know Python, just so that we can speak the same programming language. (Also, this will make type theorists and category theorists cringe. I know what I’m describing is not generic enough, but it gets the point across)
A Monad is any type which “contains” values of arbitrary other types (think: list, set, etc) - let’s call it the “container” type -, and for which it makes sense to define two operations:
unit
(you might see it calledreturn
in other places but this is very confusing, ignore it) andbind
(a.k.a>=
operator).unit
is really easy. It just takes a value of the contained type and returns our container type with that value inside it. E.g. for a list,unit(1) == [1]
,unit("a") == ["a"]
. Here is how we might defineunit
for a list:def unit(a): return [a]
bind
is a bit more complicated, but stick with me.bind
takes two arguments. The first argument is a value of our container type, and the second argument is a function which takes the contained value and returns a container (it can have a different contained type, but must have the same container type). Note that we can pass any function we like tobind
as the second argument, as long as the types are right.For example, here is how we might define
bind
for a list:def bind(lst, fun): new_lst = [] for element in lst: new_lst.extend(fun(element)) return new_lst
This definition applies the function to every element in the provided list, and returns the list with all the returned elements concatenated.
This definitely sounds weird for a list, but it does make sense in a particular context: describing computations which can produce multiple results.
Let’s say we have two functions, one plus_or_minus(a) = ±a:
def plus_or_minus(a): return [a, -a]
And another, double_or_halve(a) = a * 2^(±1):
def double_or_halve(a): return [a * 2, a / 2]
Then we can apply them in sequence, using
unit
to create our starting list, andbind
to apply the functions:bind( bind( unit(1), plus_or_minus ), double_or_halve )
What we get in the end is a “flat” list:
[
, listing all possible outcomes of our computations. This can even be slightly useful in some scientific settings. ]If this all sounds esoteric, don’t worry, for a list in Python it kind of is! However, some languages like Haskell provide a convenient syntax sugar for monads, which allow you to write monadic expressions as though you are writing imperative code, like this:
do a <- pure 1 b <- plus_or_minus a double_or_halve b
And convenient operators, to write concise code like this:
pure 1 >>= plus_or_minus >>= double_or_halve
(Haskell standard library actually provides us with a definition of Monad, and its implementation for a List, such that both these examples are equivalent to the python one)
Hopefully you can start to see that with tooling like this, even a List monad can be occasionally useful.
But the main benefit of Monads comes when you start defining them for other types from the algebraic type land. Take for example
Maybe
- haskell’s equivalent of Python’s deprecatedOptional
, or Rust’sOption
. It is a type that can either contain a value or nothing at all. In python speak, it isUnion[a, None]
It is also a monad, quite trivially so.
Here’s
unit
for this type:def unit(a): return a
And here’s
bind
:def bind(opt, fun): if opt is None: return None else: return fun(opt)
With this monad we can combine “fallible” computations, i.e. computations that can fail for some inputs.
Take for example this:
def reciprocal(a): if a == 0: return None else: return 1 / a
This “fails” when the input is zero.
def minus_one(a): return a - 1
This function never fails.
We can combine multiple operations like this, guaranteeing that they never get
None
as an input:bind( bind( bind( unit(1), minus_one ), reciprocal ), minus_one )
(this returns None, but it never calls
minus_one(None)
)Or like this:
bind( bind( bind( unit(0.5), reciprocal ), minus_one ), minus_one )
(this returns 0)
Once again this is much more elegant in Haskell:
pure 1 >>= minus_one >>= reciprocal >>= minus_one
orpure 0.5 >>= reciprocal >>= minus_one >>= minus_one
Notice how the structure of
bind
andunit
calls is the same for both the list and the optional. This allows us to write functions which operate on arbitrary monads, which makes them useful for hundreds of seemingly very different types; it also allows Haskell to make syntax sugar which makes code more readable and understandable across dozens of different domains. It unifies a lot of different concepts into one “API”, making it much easier to write generic code that encapsulates all those concepts.Just as a glimpse of how powerful this is, a typical project structure in Haskell is to define your entire application domain (serving web pages, querying an SQL server, writing data to files) as a single monadic type; explaining this takes a lot more time, effort, and monad transformers.
However, the most useful part of Monads in Haskell is difficult to describe in Python. It is the fact that Monads perfectly encapsulate the “side effects” of a program, i.e. how it interacts with the real world around us (as opposed to side-effect-less computations). Haskell actually defines all side effects as functions which take and return the entirety of the “real world” (sounds insane, but it’s actually really elegant); think something like this (python syntax):
def print(a: RealWorld, string: str) -> RealWorld: # <...>
And the side effect is then thought of as the difference between the RealWorld the function takes as argument and returns.
In order to take values from the real world (think: read the value from stdin, a-la python’s
input
), Haskell then defines anIO
type, which “contains” some valuea
which was “taken” from the real world. The details are deliberately hidden from the programmer here, so that you can’t just “unwrap” the IO value and take the “contained” value from it from your side-effect-less function; you have to be inside the IO monad to do anything with the value. Under the hood it is defined as something like this (python syntax):type IO[a] = Callable[RealWorld, (RealWorld, a)]
(don’t dwell too much on the details here, it is admittedly confusing)
So,
print
is actually defined closer to this:def print(string: str) -> IO[]:
(which can be read as: take a string and return some action in the real world, which doesn’t return anything)
And
getLine
(Haskell’s analog of python’sinput
) is defined something like this:def getLine() -> IO [str]:
(which reads: return some action in the real world, which returns a string)
This means that you can technically “call” both
print
andgetLine
from side-effect-less code, but they will simply give you an opaque value of type IO, which you can’t do anything outside the IO monad. So that you can do anything with this type, themain
function in Haskell is defined something like this:def main() -> IO[]:
This then allows you to combine
getLine
andprint
as follows:def main() -> IO[]: return bind(getLine(), print)
This reads one line from stdin and prints it to stdout.
The actual Haskell function looks like this:
main = getLine >>= print
Neither the
getLine
nor theprint
calls actually executed any code that read or printed anything; that code was executed by some mysterious “runtime” which first called themain
function, got anIO ()
operation from it, and then executed that operation in its entirety.This is probably very confusing; worry not, it will be, we’re trying to do this in Python! I swear it makes much more sense in the context of Haskell. I highly recommend reading https://learnyouahaskell.com/chapters to learn more.
As fun exercises to make sure you understand monads, think of the following:
- Can you come up with a (generic) type which isn’t a monad?
- Are functions monads? (hint: the answer is contained in this comment)
- Is it possible for a type to be a monad in multiple different ways? Think of examples.
I’d like to draw your attention to the OP image.
~Sorry, im sure its a very good description and youve clearly put a lot of effort in.~
So it’s magic. Gotcha.
Haskell’s runtime system is indeed magic, the fact it uses monads is just a consequence of monads being useful for this application. Monads themselves are not too complicated.
This sounds more complicated than what I know about monads, but also I lost my ability to explain monads when I understood it, soo… I guess this is the best we could afford.
I mean, it explains things at length, but it’s all fairly accurate.
As a senior engineers writing Haskell professionally for a number of years, I’ve found it much simpler to teach about Monads after having taught about Functors and Applicatives first, because there’s a very natural, intuitive progression, and because most people already have an intuitive grasp of Functors because
map
is cribbed so commonly in other programming languages. I’ve used this approach successfully to teach them to people completely new to Haskell in a fairly short amount of time.As a senior engineers writing Haskell professionally for a number of years, I’ve found it much simpler to teach about Monads after having taught about Functors and Applicatives first, because there’s a very natural, intuitive progression, and because most people already have an intuitive grasp of Functors because map is cribbed so commonly in other programming languages.
I agree! I just wanted to explain what Monads are, standalone, and avoid introducing any other concepts.
What part do you think is more complicated than your understanding? I’d love to fix it to make it as simple and understandable as possible.
http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
It’s a “programmable semicolon” or “decorated-function composition”. I think most people that are confused about it, are trying to make it be more meaningful than it is. Haskell (?) just grabbed a math name so they’d have one word for it, because it’s a useful class name there.
It’s a “programmable semicolon” or “decorated-function composition”
“programmable semicolons” is wrong, you can have that without Monads, and in fact Haskell has a do-notation for Applicative. “decorated function composition” is a bit vague, I think I see what you mean but it’s not too helpful.
It’s just an interface for a generic type that requires two specific functions to be implemented. Understanding why it is so useful is the tricky part.
I switched to the simple English Wikipedia article:
https://simple.wikipedia.org/wiki/Monad_(functional_programming)
Monad is (a classes of type of) a collapsible container. Collapsible, like how you can flatten a nested list, or Option<Option<A>> can be flattened to Option<A>.
A common pattern with monads is the flatMap function, where you apply function A -> List<B> to (each element of) List<A> to obtain List<B>. This happen to represent erroneous call chaining with Option or Result types.
This is actually a good alternative explanation, as long as you clarify it a bit.
What is meant by “container” type is that it’s possible to
- “Put” a single thing into our container (
unit
/pure
) - “Modify” the insides our container (
map
/fmap
), without “taking any values out”
Take for example a list.
unit
is simple, as I’ve described in my comment:def unit(a): return a
Map is also fairly straight-forward, just applying a given function to every element of the list:
def map(fun, lst): new_lst = [] for element in lst: new_lst.append(fun(element)) return new_lst
Then your final operation (“flattening” the container) is traditionally called
join
. It concatenates a list of lists into a single flat list:def join(lst): new_lst = [] for element in lst: new_lst.extend(element) return new_lst
(you might notice that it is extremely similar to both
map
andbind
)This allows us to define
bind
from my other comment (which you callflatMap
) in terms ofjoin
andmap
:def bind(lst, fun): return join(map(fun, lst))
Or, if you already have a
bind
(andunit
) defined, you can definejoin
andmap
as follows:def join(lst): return bind(lst, unit) def map(fun, lst): return bind(lst, lambda x: unit(fun(x)))
Showing that a type defining
unit
andbind
is equivalent to a type definingunit
,join
andmap
- they are both equivalent definitions of a monad, you can derive one from another and vice versa.Historically, the
unit
+bind
definition is more popular and aligns with what most functional languages do, so I went with it for my explanation. But yours is probably a bit easier to understand for an FP outsider.- “Put” a single thing into our container (
I liked this explanation:
It’s like a burrito
They’re just an abstraction to model and contain implicits, for instance like IO (filesystems) or program state. Regular functions should produce the same output for the same input, if they do not you have implicit effects. You can think of it kinda like a class, or a context, or a module which has some functions to help work with the implicit effect. Central then is also that each such class, context, or module can be composed with others of the same kind, maintaining a lineage of the implicit effect.
If I squint I can see what you mean, but it can apply to many different concepts (effects, capabilities, etc) and not just monads. Monads have a very specific and actually quite simple definition.
Like we’re dumb!
Not like we’re smart!
I shall maintain that is the dolt explanation
I know! That’s the Greek mythology thing.
You should crosspost this to !philosophymemes@quokk.au