I am trying to get a deeper understanding of monads. So I started digging a little into Maybe the Monad.
There is one thing that I do not seem to understand. Read it:
“Thus, a possible link leads to a short circuit. If in any chain of operations any of them returns Nothing, the evaluation stops and nothing returns from the whole chain.”
From: http://mikehadlow.blogspot.com/2011/01/monads-in-c-5-maybe.html
And this:
"For type Maybe<T> binding is implemented in accordance with a simple rule: if the chain returns an empty value at some point, further steps in the chain are ignored, and an empty value is returned instead"
From: "Functional Programming in C #" http://www.amazon.com/Functional-Programming-Techniques-Projects-Programmer/dp/0470744588/
Ok, let's look at the code. Here is my, perhaps, monad:
public class Maybe<T> { public static readonly Maybe<T> Empty = new Maybe<T>(); public Maybe(T value) { Value = value; } private Maybe() { } public bool HasValue() { return !EqualityComparer<T>.Default.Equals(Value, default(T)); } public T Value { get; private set; } public Maybe<R> Bind<R>(Func<T, Maybe<R>> apply) { return HasValue() ? apply(Value) : Maybe<R>.Empty; } } public static class MaybeExtensions { public static Maybe<T> ToMaybe<T>(this T value) { return new Maybe<T>(value); } }
And here is my sample code using the monad:
class Program { static void Main(string[] args) { var node = new Node("1", new Node("2", new Node("3", new Node("4", null)))); var childNode = node.ChildNode .ToMaybe() .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()); Console.WriteLine(childNode.HasValue() ? childNode.Value.Value : ""); Console.ReadLine(); } } public class Node { public Node(string value, Node childNode) { Value = value; ChildNode = childNode; } public string Value { get; set; } public Node ChildNode { get; private set; } }
It is clear that we are trying to dig deeper into the tree of nodes than is possible. However, I do not see how it acts in accordance with the quotes that I mentioned. I mean, of course, I took into account zero checks, and the example works. However, this does not break the chain early. If you set breakpoints, you will see that each Bind() operation will be used without value for recent operations. But this means that if I dig 20 levels in depth, and in fact it is only 3 levels, will I still check 20 levels or am I wrong?
Compare this with the non-monad approach:
if (node.ChildNode != null && node.ChildNode.ChildNode != null && node.ChildNode.ChildNode.ChildNode != null) { Console.WriteLine(node.ChildNode.ChildNode.ChildNode.Value); }
Isn't that what should be called a short circuit? Because in this case, if really breaks at a level where the first value is zero.
Can anyone help me figure this out?
UPDATE
As Patrick noted, yes, it’s true that each binding will be involved, even if we have only 3 levels and we will try to go through 20 levels. However, the actual expression provided for calling Bind () will not be evaluated. We can edit the example to clarify the effect:
var childNode = node.ChildNode .ToMaybe() .Bind(x => { Console.WriteLine("We will see this"); return x.ChildNode.ToMaybe(); }) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => x.ChildNode.ToMaybe()) .Bind(x => { Console.WriteLine("We won't see this"); return x.ChildNode.ToMaybe(); });