Earlier quoted context omitted.
This [1] might help 'get it' with regards to monads. Very easy to digest. You mention LINQ and the List abstraction. Yes IEnumerable is a monad (LINQ isn't in itself - it [the grammar] is the equivalent of 'do' notation in Haskell). Monads are simply 'wrapper types' that follow a couple of rules: 1. You must be able to construct one from the un-wrapped value (return in Haskell, new List (...) in C#) 2. It must implem…
Thanks for taking the time to write all that up. I think (too early to say) that seeing all this expressed in C#/Java(script)/Ruby/Python syntax is key for someone like me. I learned LINQ/Select(Many) and later all the map/filter/reduce functional goodness by playing with Clojure and never had a problem and never heard the word "monad" and was fine, totally fine. Later watching a video on Rx (MS's reactive extensions…
public class Option
{
public readonly bool HasValue;
public readonly T Value;
internal Option(bool hasValue, T value)
{
HasValue = hasValue;
Value = value;
}
public Option Select(Func map) =>
HasValue
? Option.Some(map(Value))
: Option.None();
public Option SelectMany(Func> bind, Func project) =>
HasValue
? bind(Value).Select(u => project(Value,u))
: Option.None();
}
public static class Option
{
public static Option Some(T value) =>
new Option(true, value);
public static Option None() =>
new Option(false, default(T));
}
The SelectMany implementation is slightly more complicated than I showed before. This is an optimisation that C# does to group the bind and map together. So it may look slightly scary as a function. But hopefully you can see that if the Option has a value then it first invokes bind, then uses the result of the bind (an Option) to project the final result. Here's a more imperative version of it: public Option SelectMany(Func> bind, Func project)
{
if (HasValue)
{
var u = bind(Value);
if (u.HasValue)
{
return Option.Some(project(Value, u.Value));
}
else
{
return Option.None();
}
}
else
{
return Option.None();
}
}
You can see that with the IEnumerable version of Select and SelectMany it encapsulates list iteration. With the Option monad it doesn't do that. It instead checks the HasValue field, and if it's false then it doesn't run the map or bind functions.The second static class: Option, contains the 'return' functions: Some or None. These wrap a value of type T in an Option.
Now if we use Option in a LINQ expression:
var option1 = Option.Some(10);
var option2 = Option.Some(10);
var none = Option.None();
var res1 = from x in option1
from y in option2
select x + y;
// res1.HasValue == true res1.Value == 20
var res2 = from x in option1
from y in none
select x + y;
// res2.HasValue == false
var res3 = from x in none
from y in option2
select x + y;
// res3.HasValue == false
This is the same as using do notation in Haskell: do x
If we were to do that imperatively it would look like this: var res = Option.None();
if( option1.HasValue )
{
if( option2.HasValue )
{
res = Option.Some(option1.Value + option2.Value);
}
}
Clearly more cluttered and error prone and importantly, not composable. This is where the notion of 'programmable semi-colons' comes from. It appears that the monad is running behaviour 'between the lines', and it is.Hopefully that clears the fog. I'll keep an eye on this thread for a few days, so feel free to drop any questions in here or on my project page.