How does monastic monad act as a short circuit?

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(); }); 
+7
source share
3 answers

I have an implementation of a possible monad in C #, which is slightly different from yours, firstly, it is not tied to null checks, I believe that my implementation is closer to what happens in a standard implementation, for example, in Haskel.

My implementation:

 public abstract class Maybe<T> { public static readonly Maybe<T> Nothing = new NothingMaybe(); public static Maybe<T> Just(T value) { return new JustMaybe(value); } public abstract Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder); private class JustMaybe : Maybe<T> { readonly T value; public JustMaybe(T value) { this.value = value; } public override Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder) { return binder(this.value); } } private class NothingMaybe : Maybe<T> { public override Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder) { return Maybe<T2>.Nothing; } } } 

As you can see here, the NothingMaybe binding function simply returns a new nothing, so the value passed to the binding expression is never evaluated. This is a short circuit in the sense that more binding expressions will not be evaluated once you are in the “nothing” state, however, the Bind function itself will be called for each monad in the chain.

This implementation can be used for any type of "undefined operation", for example, null checking or checking for an empty string, so all these different types of operations can be combined in a chain:

 public static class Maybe { public static Maybe<T> NotNull<T>(T value) where T : class { return value != null ? Maybe<T>.Just(value) : Maybe<T>.Nothing; } public static Maybe<string> NotEmpty(string value) { return value.Length != 0 ? Maybe<string>.Just(value) : Maybe<string>.Nothing; } } string foo = "whatever"; Maybe.NotNull(foo).Bind(x => Maybe.NotEmpty(x)).Bind(x => { Console.WriteLine(x); return Maybe<string>.Just(x); }); 

This will print “anything” to the console, however, if the value is zero or empty, it will do nothing.

+11
source

As I understand it, all Bind methods will be called, but the provided expressions will be evaluated only if the previous one returns a value. This means that Bind methods that are called after one that returns null (or more correctly: default(T) ) will be very cheap.

+4
source

We can do it more tricky.

Create an interface derived from IEnumerable

 public interface IOptional<T>: IEnumerable<T> {} 

This will maintain compatibility with LINQ methods.

 public class Maybe<T>: IOptional<T> { private readonly IEnumerable<T> _element; public Maybe(T element) : this(new T[1] { element }) {} public Maybe() : this(new T[0]) {} private Maybe(T[] element) { _element = element; } public IEnumerator<T> GetEnumerator() { return _element.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

After that we can use the full power of LINQ and do it

 var node = new Node("1", new Node("2", new Node("3", new Node("4", null)))); var childNode = new Some<Node>(node.ChildNode) .SelectMany(n => new Maybe<Node>(n.ChildNode)) .SelectMany(n => new Maybe<Node>(n.ChildNode)) .SelectMany(n => new Maybe<Node>(n.ChildNode)) .SelectMany(n => new Maybe<Node>(n.ChildNode)) .SelectMany(n => new Maybe<Node>(n.ChildNode)); Console.WriteLine(childNode.Any() ? childNode.First().Value : ""); 
0
source

Source: https://habr.com/ru/post/902438/


All Articles