Maybe monad如何作为短路?

Chr*_*oph 8 c# monads functional-programming maybe

我正在努力深入了解Monads.因此,我开始挖掘一下Maybe Monad.

有一件事我似乎没有做对.读这个:

"因此,Maybe Bind会发生短路.在任何一系列操作中,如果其中任何一个返回Nothing,评估将停止,并且整个链条都不会返回任何内容."

来自:http://mikehadlow.blogspot.com/2011/01/monads-in-c-5-maybe.html

还有这个:

"对于Maybe<T>类型,绑定是根据一个简单的规则实现的:如果链在某个点返回一个空值,则链中的其他步骤将被忽略,而空值则是returend"

来自:"C#中的功能编程" http://www.amazon.com/Functional-Programming-Techniques-Projects-Programmer/dp/0470744588/

好吧,让我们来看看代码.这是我的可能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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我使用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; }
}
Run Code Online (Sandbox Code Playgroud)

很明显,我们正在尝试深入挖掘节点树,而不是可能.但是,根据我提到的报价,我没有看到它是如何运作的.我的意思是,当然我已经考虑了空检查并且示例有效.但是,它并没有提前打破链条.如果设置断点,您将看到Bind()将使用每个操作,因此没有最后一个操作的值.但这意味着,如果我挖20级深度并且实际上只下降3级,我仍然会检查20级或我错了吗?

将此与非monad方法进行比较:

        if (node.ChildNode != null
            && node.ChildNode.ChildNode != null
            && node.ChildNode.ChildNode.ChildNode != null)
        {
            Console.WriteLine(node.ChildNode.ChildNode.ChildNode.Value);
        }
Run Code Online (Sandbox Code Playgroud)

这实际上不应该被称为短路吗?因为在这种情况下,if确实在第一个值为null的级别中断.

任何人都可以帮助我明白这一点吗?

UPDATE

正如Patrik指出的那样,即使我们只有3个级别并试图深入20个级别,也会调用每个绑定.但是,不会评估提供给Bind()调用的实际表达式.我们可以编辑示例以使效果清晰:

        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();
                      });
Run Code Online (Sandbox Code Playgroud)

Pat*_*gne 12

我在c#中实现了可能monad,与你的有点不同,首先它与null检查无关,我相信我的实现更接近于类似Haskel中的标准可能实现中发生的情况.

我的实施:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你在这里看到的那样,NothingMaybe的bind函数只返回一个新的东西,所以在binder表达式中传递永远不会被评估.它是短路的,因为一旦你进入"无状态"就不会再评估绑定表达式,但是绑定函数本身将被链中的每个monad调用.

这种实现可以用于任何类型的"不确定操作",例如空检查或检查空字符串,这样所有这些不同类型的操作可以链接在一起:

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); });
Run Code Online (Sandbox Code Playgroud)

这将向控制台打印"what",但是如果值为null或为空,则它将不执行任何操作.


Fre*_*örk 5

据我了解,所有Bind方法都将被调用,但是仅当前一个返回值时,才会对提供的表达式进行求值。这意味着Bind在返回一个null(或更正确地说:)的方法之后调用的方法default(T)将非常便宜。