I must be insane to take this on so with so few followers, but here goes...
A Monad is a simple yet really helpful abstraction that you already know if you use LINQ in C#, you just do not realize it yet. They are not scary or difficult to understand.
LINQ in C# was influenced by Haskell and Category Theory, the naming used was changed to a more SQL like syntax so it did not scare people away.
The following is just a different way of looking at what you already know.
What is a Monad?
There are many types of Monad, one of which is IEnumerable. A Monad is just a generic class that wraps up value(s) of type T and implement a specific interface.
To be a Monad it must implement two functions:
- return - In C# a constructor.
- bind - In C# this is LINQ SelectMany().
There are some rules to follow but on the whole they are not important for this article. If people want more in-depth detail I will do a follow up piece.
If you already use LINQ you do this stuff every day without second thought.
// These are the C# version of return
// The list can be empty
IEnumerable<int> set1 = new int[0];
IEnumerable<int> set2 = new[] {1};
IEnumerable<int> set3 = new[] {1, 2, 3};
IEnumerable<int> set4 = new List<int> {1, 2, 3);
// And these are Bind
var result1 = people.SelectMany(person => person.EmailAddresses);
var result2 =
from x in Enumerable.Range(1, 10)
from y in Enumerable.Range(1, 10)
select Tuple.Create(x, Y);
If you have tried to read Monad tutorials before you will have seen stuff like this:
(a -> Mb) -> Ma -> Mb
Yep, your eyes bleed unless you have learnt to read it. So we will not do that, instead we will look at the C# SelectMany function signature:
IEnumerable<t2> SelectMany<T1, T2>(
this IEnumerable<T1> collection,
Func<T1, IEnumerable<T2>> selector);
// This is the following:
// Ma -> (a -> Mb) -> Mb
C# uses a different order for its parameters because it uses extension methods for composition.
- M is IEnumerable
- T1 is a
- T2 is b
- collection in Ma
- selector is (a -> Mb)
- The returned result is MB
Or if we rewrite SlectMany() in a more generic way:
Monad<t2> SelectMany<T1, T2>(
this Monad<T1> monad,
Func<T1, Monad<T2>> selector);
LINQ is not just about IEnumerable though.
Fun with Lazy
Lazy is part of the core C# library, a wrapper class,. You give it a function to perform some calculation, normally costly. The calculation is only performed when you ask for it..
var lazyValue = new Lazy<int>(
() =>
{
// Some long arse calculation
return theValue;
};
// Execute the long arsed calculation
var result = lazyValue.Value;
I will now skip right to the punchline. Via LINQ we can combine these Lazy calculations to produce new ones and still not run anything until the value is requested.
var lazyValue1 = new Lazy<int>(() => 5);
var lazyValue2 = new Lazy<int>(() => 6);
var lazyValue3 = new Lazy<int>(() => 7);
// Create a new Lazy that will add the lazy values together.
// We do this with the following query. None of the lazy
// calculation functions are run here.
var lazyAdd =
from value1 in lazyValue1
from value2 in lazyValue2
from value3 in lazyValue3
select value1 + value2 + value3;
// Now execute them all
var result = lazyAdd.Value;
Wow, that is a mountain of complexity we have hidden away. Here is how to read it.
- from value? in lazyValue1?- provide a means to get the value from lazyValue?
- select value1 + value2 + value3 - Create a new function to add them together and wrap it in a Lazy
So to get all of this power hidden away all we write is a single function, SelectMany(). Get ready, it is hideously complex!
public static Lazy<T2> SelectMany<T1, T2>(
this Lazy<T1> lazyValue, Func<T1, Lazy<T2>> selector)
=> new Lazy(() => selector(lazyValue.Value).Value;
Wait, what? Is that it?
Yep. It creates a new Lazy object where the calculation does the following:
- Get the current lazy value via "lazyValue.Value"
- Run the selector and get its lazy value. "selector(lazyValue.Value).Value" does this.
While we are here we will implement Select also. Not required for a Monad but handy to have in LINQ. We will use SelectMany() to build Select()
public static Lazy<T2> Select<T1, T2>(
this Lazy<T1> lazyValue, Func<T1, T2> selector)
=> lazyValue.SelectMany(
value => new Lazy(() => selector(value));
}
There is also a second version of SelectMany we could define but it is really an optimization when composing bigger compositions. It can be inferred by LINQ from the one we have provided. That one is Beyond the scope of this article.
So with just SelectMany() we have made Lazy operate with LINQ, we have also made the Lazy Monad.
Conclusion
A Monad is a wrapper that adds context to a value. For IEnumerable it manages access to the list, for Lazy it manages control over lay evaluation.
Some of the more common Monads are:
- Maybe - Never have to deal with null again in your code! NEVER
- Either - Allow a function to return and result or error and that control if further calculation takes place.
- Reader - Gain access to global state in a safe way
- Writer - Add logging or update global state in a safe way
- State - This is just a mind feck. Took me a year, on and off, to really get comfortable with it!!!!
There are many other things we can do with them.
Monads are just a wrapper class and two functions but they can hide huge complexity from the caller and deal with it for them. LINQ is built from the ground up to handle and compose Monads, you just need to understand how.
Do you want me to take this further?
Woz