It is said that there’s a curse with Monads.. When you finally understand it, you’ll lose the ability to explain it to others. This curse is made worse by the fact that after you understand it, you’ll begin to see it everywhere
Well I am going to try and break that curse. This blog is dedicated to people who just want to build real world applications and aren’t exactly interested in Academic Exercises. So I’m going to save you all the mystery and try to explain it in three simple things.
- Type Amplification
- Chaining Computation
- Chaining Computation on Amplified Types
Type amplification is the “lifting” of types into a context. It’s sort of a Type wrapper. See the example below:
You can then say that the Monad<T> is an amplified type. But for this to be useful, we need some way/function that will allow us to convert values of any type into values of the Monad. This function is called the Unit Function. Let’s see some examples.
The rule goes that once you’ve lifted a type, you can’t get the original value back.
An analogy would be paying cash to the bank. Once the cashier takes the money from you and credits your account, you can’t get the original cash back.
But then how do I spend my Monad if I can’t get my value back?
The same way banks give you credit cards to spend your money, Monads provide a way of doing stuff with your values. This is the Map function. This function allows you to do with the Monad whatever you would have done with the original value.
This is really cool because we can perform all our computations on amplified types which gives us a lot of flexibility. “So can we now replace all references to raw values with their Monad counterparts?”.. Erm.. Not quite. We currently have no way to perform computation on two Monads.
Chaining Computation on Amplified Types
From the last example, what if I wanted to subtract result1 from result2? I have no way of getting at their internal value. So what’s next? Well that’s where the Bind method comes into play
That’s it! Bind is typically called the “God Function”
That’s because in conjunction with the Unit function, you can express/do just about anything. For example, you can re-create the Map function by calling bind on a function that take the Monad’s value and amplifies the result of the computation. See below:
This is really Cool Stuff.. But what’s the value of all this?
This principle of performing computations on amplified types isn’t new and it might even be familiar. As a matter of fact, you’ve used them.. A LOT
Your favorite things like Seq/IEnumerable, Task, Observable and Option are all monads. Think about it, IEnumerable and Observable have SelectMany, Seq has collect, Task has ContinueWith, and Option has bind. IEnumerable amplifies values by having zero or more values, Task amplifies a value by resolving the value in the future. Option amplifies a value by letting you know it might be null, Observable amplifies types by firing multiple values as events. When you finally get to this realization, It fundamentally changes your perspective.
Monads and Purely functional Languages
That way you can only chain computation on the monad but you never have direct access to the impure value and that keeps all your functions pure. It might seem like a hassle but the language has a bind operator and some other syntax sugar to make things simple.
F# provides what is called a Computation Expression to make things simpler. You have definitely used it before. e.g.
In fact, seq is not hard coded into the compiler. You can just as easily create your own expressions and the compiler does all the calls to bind for you.
Believe it or not, C# also provides something similar. And that‘s the LINQ Syntax. Anders Hejlsberg, Eric Meijer and co dressed a wolf in sheep clothing. Contrary to what you might think, the LINQ Syntax isn’t just for writing LINQ queries, you can use it to do Monad Computations (although in a limited manner). .
I really hope I was able to explain it in a way that almost anyone can understand. This pattern isn’t exclusive to functional languages like F#. You can use it in many mainstream languages like C#. I even wrote a library using the Monad pattern in C# (Operation Extensions) long before I started coding F#.