12 comments

[ 4.3 ms ] story [ 39.5 ms ] thread
(comment deleted)
Do you really want to understand monads? Here is how to do that in 2 steps.

1) Go to https://www.haskell.org/ and install haskell for your platform

2) Go to http://learnyouahaskell.com/ and work through the book.

I guarantee that by the time you finish going through it, you will have a good understanding of Monads.

Trying to understand monads when you have only worked with imperative languages is like trying to understand heterosexual sex without ever having seen a member of the opposite sex.

The correct answer to the question "what's a monad?" is not "learn haskell". That answer misses the point and is not helpful at all.
The conclusion of the post is, literally, that you don't need to understand monads. Not what the title sells.
I've been trying to find idiomatic ways of explaining monads to people who already know how to program, but may not be using functional languages. Possibly this explanation will be useful. I would be happy for some criticism.

A monad is a type of functor. In terms of programming a functor is a collection: a data structure that can hold a piece of information. Functors have a function related to them, usually called "map" or "fmap" (for 'functor map'). "map" takes the information out of the collection, transforms it with a function that you pass to "map" and then puts it back in to the same kind of collection. Most programmers have experience with this if they use a modern programming language (though they may not have known it was a "functor" that they were using).

Quick note: I always found the word "functor" to be confusing because it sounds like it should do something rather than be something. This is true in category theory where the concept originates, but the computer programming implementation turns out differently. I'm going to try to avoid category theory in this explanation, but it's still interesting to look into.

Like I said, a monad is just a special type of functor. With a functor, it is possible to change the type of the information you are working on with the function you pass to "map". You might have a function that converts an character to an integer (maybe just giving its ASCII representation). Then you can pass that function to map and your collection of characters will be converted to a collection of integers. Monads (used as monads) don't allow this.

Monads use a function called "bind". It works a bit like "map" except that the function you pass to "bind" needs to return the resultant monad rather than just the transformed information. For example, if you have a container that contains an integer, you need to pass a function to "bind" that returns a container that contains an integer. You can transform the integer in the function, but you must always return a container with the integer inside.

Why do you want a monad? It is so you can chain actions knowing that bind will always return the exact same type. A place where I often build monads is in refining a search. I start with a collection of data. I call "bind" with the refinement function. This will return a collection of data. I call "bind" with the next refinement function. I just keep doing that until I've finished the refinement and I'm left with the refined collection of data. I can always add more refinements to the chain because I know that "bind" will always return a container of search results.

Implementation detail: You might be wondering why "bind" has to return a monad, while "map" just returns the transformed data (after which "map" puts it back into the functor). "map"'s behaviour seems much more convenient. The reason is so you can handle data structures that have optional data. Imagine our monad can contain either search results or an error. If the monad returns search results, "bind" will call the function passed to it. Otherwise it will just return the monad containing the error. This allows you to return a monad containing an error from your function. If you were using "map", there is no way to return that error.

And that's basically it. Collections are almost always functors meaning that you can define map on them. You use map to apply general transformations on the data in the collection. Most functors can also be monads which means you can implement bind on them. You use bind to successively apply operations on a data structure, always getting the exact same type of data structure back. bind forces the function passed it it to return a monad, which allows you to store optional information in the monad.

Fina...

Thanks that made some things clear to me. I had to spontaneously think about Rust’s Iterator interfaces where you also have a map which you can use with closures.
You made it very clear, thank you. However, do you have more examples of what they’re used for? Given your example, I still don’t understand why monads are so important.
I think this is the why monads are hard to understand. It's hard to come up with a general example of why they are useful and anything beyond they significantly simplify a lot of common cases you run into with functional programming quickly get convoluted.

I didn't really understand monads until I ran into really long / repetitive / ugly code when writing Haskell and then reading a book and realizing they could be used to simplify the parts of my code that frustrated me.

I think the biggest reason they are useful is because data and the container around it can be treated independently and gives a good common interface for container behavior.

They are important in Haskell because they allow "real world" tasks to performed and give the ability to reason about functions.

They are _useful_ for non-pure function programmers because it's a tool in the tool box which can allow them to write composable code that can be reasoned about.

Java's & C++'s Optionals are monads, Rust's Result<T, E> is a monad, etc.

The main reason for using monads is to "wrap" program state. This allows you to defer your computation (or even do it on another processor, for example). Later you can "unwrap" the result.

There are lots of concrete examples, which often have names. For example "Maybe" is just a container that holds some data or "Nothing" (you can implement "Nothing" with nil/null). When you pass a function to bind, it doesn't run if the container holds "Nothing". It just returns the container with "Nothing". Otherwise the function is run and you can do your computation (returning a container with the result or "Nothing").

Instead of a container that holds data or "Nothing", you can have a container that holds data or an error. This is usually called "Either" (either data or an error), but lately some languages (like Rust) are calling it "Result" which I like better. It's a really simple thing, but dealing with return values that might be errors is a really common problem.

You might be familiar with the technique of dealing with errors using exceptions. With this technique, you always return a value, or you throw an exception (which causes the execution to jump to an error handler). Throwing an exception is an example of a "side effect". The function doesn't always do the same thing -- sometimes it stops half way through and jumps up the stack. One of the main uses of monads is to "wrap" that side effect. You can see that "Either/Result" wraps the return value to allow you deal with the error without having a side effect. You can think of it as a "structured exception" (i.e. you can use normal control structures and you don't need a GOTO). Any time you have a side effect in code, you can actually wrap it in a monad. This makes the code execution much easier to reason about.

As another example, remember I said that monads let you defer computation to a time that is convenient for you. A "Promise" is a monad that wraps a temporal side effect. You return a value that may or may not be filled yet. When you try to run bind on the monad (usually renamed to "then") it blocks the current thread of execution until the value is available.

One of the nice things about monads is because they are just containers, you can easily build monads of monads. For example, imagine a Promise whose contained value is an Either/Result. There are lots of common combinations. To give you some ideas, remember my search result refining example. You can imagine a "set of data" that doesn't contain anything at first. When you "refine" the search, it does a network request that returns a Promise. However, you don't actually have to do the network request at that time. You can just return the Promise. Then you go to the next refinement. This allows you to compose all the refinements and when you are all done then make the network requests. Because your result is just a data structure, you can pass it around to a different part of your program, or even shuffle it off to a different processor before you evaluate it (causing it to do the network requests).

It's this kind of composition that makes monads so powerful. It might be difficult to imagine, but most containers are monads. If you can write a meaningful "bind" function for the container, then it is a monad. Even a closure is a monad. If you partially apply some parameters and return a function, that function is just a container that contains those parameters. You can write "bind" that composes that partially applied function to another function. In this way you can build up large functions. An example of this would be a DSL for validating data -- you can compose all of the functions at program initialisation time and run the result when validating your data.

I think the most important thing to understan...

As a programmer with 20+ years of experience with various non-functional languages I have been trying to understand monads, and the main problem has always been that most of the explanations are similar long-winded diatribes making things even more complicated by using terminology that needs further explaining (rarely given) itself.

After many such articles, my main -- very generalised, I'm aware -- conclusion is that monad is basically a programming construct that a) contains some data and b) has some associated operations to be performed on that data.

Basically, a monad is an object.

that's a good generalization but has one small thing missing.

The main benefit of a monad is composition. It allows a programmer to chain computation and put some logic in the chaining. It's the difference between

    f(g(h(x)))
and

    h(x).then(g).then(f)
the '.then' method has some inbuilt logic, for to ability to add that logic we 'embellish' the return type of h (or put it in a container like Promise<>).

To equate it to java's Optionals, the flatMap is a good example, it takes an optional and applies some logic and decides to call the second function.