C# 单子是如何起短路作用的?

C# 单子是如何起短路作用的?,c#,functional-programming,monads,maybe,C#,Functional Programming,Monads,Maybe,我想对单子有更深入的了解。因此,我开始深入研究单子 有一件事我似乎做得不对。请阅读以下内容: 因此,maybine绑定会起短路作用。在任何操作链中,如果其中任何一个操作没有返回任何内容,则计算将停止,整个操作链也不会返回任何内容 发件人: 这是: “对于Maybe类型,绑定是根据以下简单规则实现的:如果链在某个点返回空值,则忽略链中的进一步步骤,而返回空值” 摘自:“C#中的函数式编程” 好的,让我们看看代码。这是我的单子: public class Maybe<T> { p

我想对单子有更深入的了解。因此,我开始深入研究单子

有一件事我似乎做得不对。请阅读以下内容:

因此,maybine绑定会起短路作用。在任何操作链中,如果其中任何一个操作没有返回任何内容,则计算将停止,整个操作链也不会返回任何内容

发件人:

这是:

“对于
Maybe
类型,绑定是根据以下简单规则实现的:如果链在某个点返回空值,则忽略链中的进一步步骤,而返回空值”

摘自:“C#中的函数式编程”

好的,让我们看看代码。这是我的单子:

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);
    }
}
很明显,我们正试图比可能的更深入地挖掘节点树。然而,根据我提到的引文,我看不出它是如何运作的。我的意思是,当然,我已经排除了空检查,并且示例是有效的。然而,它并没有过早地断开链条。如果设置断点,您将看到将使用每个
Bind()
操作,因此最后的操作没有值。但这意味着,如果我挖20层深,实际上只下3层,我仍然会检查20层,还是我错了

将其与非单子方法进行比较:

        if (node.ChildNode != null
            && node.ChildNode.ChildNode != null
            && node.ChildNode.ChildNode.ChildNode != null)
        {
            Console.WriteLine(node.ChildNode.ChildNode.ChildNode.Value);
        }
这不就是所谓的短路吗?因为在这种情况下,if在第一个值为null的级别上真正中断

有人能帮我弄清楚吗

更新

正如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();
                      });

据我所知,所有的
Bind
方法都会被调用,但是提供的表达式只有在前一个返回值时才会被计算。这意味着在返回
null
(或者更准确地说:
default(T)
)后调用的
Bind
方法将非常便宜。

我有一个c语言中maybe 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;
        }
    }
}
公共抽象类
{
public static readonly Maybe Nothing=new Nothing Maybe();
公共静态可能只是(T值)
{
返回新的JustMaybe(值);
}
公开摘要可绑定(Func-binder);
也许是私人舱
:也许
{
只读T值;
公共价值(T值)
{
这个值=值;
}
公共覆盖绑定(Func绑定器)
{
返回活页夹(该值);
}
}
也许什么都没有
:也许
{
公共覆盖绑定(Func绑定器)
{
也许会回来,什么都没有;
}
}
}
正如您在这里看到的,NothingMaybe的bind函数只返回一个新的nothing,所以在binder表达式中传递的nothing永远不会被计算。从某种意义上说,当您进入“nothing状态”时,不再计算绑定表达式,但是绑定函数本身将为链中的每个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); });
公共静态类
{
公共静态可能不为null(T值),其中T:class
{
返回值!=null?可能。只是(值):可能。没有;
}
公共静态可能不空(字符串值)
{
返回值。长度!=0?可能。只是(值):可能。无;
}
}
string foo=“无论如何”;
Maybe.NotNull(foo.Bind(x=>Maybe.NotEmpty(x)).Bind(x=>{Console.WriteLine(x);返回Maybe.Just(x);});

这会将“whatever”打印到控制台,但是如果该值为null或空,则不会执行任何操作。

我们可以更巧妙地执行此操作

从IEnumerable派生的写接口

public interface IOptional<T>: IEnumerable<T> {}
public接口:IEnumerable{}
这将保存与LINQ方法的兼容性

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();
    }
}
公共类可能:IOP
{
私有只读IEnumerable _元素;
公共元素(T元素)
:this(新的T[1]{element})
{}
公众可能
:此(新T[0])
{}
私有可能(T[]元素)
{
_元素=元素;
}
公共IEnumerator GetEnumerator()
{
返回_元素。GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
返回GetEnumerator();
}
}
在这之后,我们可以使用LINQ的全部功能,这样做

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 : "");
var节点=新节点(“1”,新节点(“2”,新节点(“3”,新节点(“4”,空)));
变量childNode=
新建一些(node.ChildNode)
.SelectMany(n=>newmaybe(n.ChildNode))
.SelectMany(n=>newmaybe(n.ChildNode))
.SelectMany(n=>newmaybe(n.ChildNode))
.SelectMany(n=>newmaybe(n.ChildNode))
.SelectMany(n=>newmaybe(n.ChildNode));
Console.WriteLine(childNode.Any()?childNode.First()。值:“”);

真棒的答案。特别是在Console.WriteLine()中,这一点非常清楚+感谢您在本文中提供的更多信息。谢谢您的回答。是的,你说得对。但帕特里克的回答更清楚了。但我还是给了你一票;-)
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 : "");