Live data from Hacker News

Monads you can understand (2015)

web.archive.org

11–13 of 13 posts

Re: Monads you can understand (2015)

#11

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 "…

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 tha…

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.

Re: Monads you can understand (2015)

#12
post #7

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 "…

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.

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 is a monad, etc.

Re: Monads you can understand (2015)

#13
post #7

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 "…

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.

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 understand is that monads aren't really that complicated. They are containers for which you can write a "bind" function that allows you to chain transformations. It's the consequences of that simple fact that is really amazing. It allows you to put anything in a container -- program state, side effects, executable code, etc and then pass it around like any other data structure. Whenever you think, "This code would be much simpler if I could have X, Y before I do Z, but I don't have them", usually there is a way to put the program state you need for X and Y into a monad and pass it to Z. The result is code that is much easier to reason about.

And just because it's easy to forget: monads are functors. Functors are containers with "map". "map" allows you to transform data in a container and get back a container with that transformed data. monads use "bind" which guarantees that the returned value is exactly the same type as the original monad. This allows you to chain operations. Normally you will use monads for chaining operations and functors for general transformation. But of course they are the same container.

Post reply on HN