c# - Maybe monad 如何充当短路?

标签 c# functional-programming monads option-type

我正在尝试更深入地了解 Monad。因此,我开始深入研究 Maybe Monad。

有一件事我似乎不太对。读这个:

“因此 Maybe Bind 起到了短路作用。在任何操作链中,如果其中任何一个返回 Nothing,则评估将停止并且整个链将返回 Nothing。”

发件人:http://mikehadlow.blogspot.com/2011/01/monads-in-c-5-maybe.html

还有这个:

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

来自:“C# 中的函数式编程”http://www.amazon.com/Functional-Programming-Techniques-Projects-Programmer/dp/0470744588/

好的,让我们看一下代码。这是我的 Maybe 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);
    }
}

这是我使用 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; }
}

很明显,我们正在尝试尽可能深入地挖掘节点树。但是,根据我提到的引述,我看不出它是如何运作的。我的意思是,我当然已经排除了空值检查并且示例有效。然而,它并没有提前打破链条。如果您设置断点,您将看到每个 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);
        }

这其实不应该叫短路吗?因为在这种情况下,if 确实在第一个值为 null 的级别中断。

有人能帮我弄清楚吗?

更新

正如 Patrik 指出的那样,是的,即使我们只有 3 个级别并尝试深入 20 个级别,每个绑定(bind)都会被调用。但是,不会评估提供给 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();
                      });

最佳答案

我在 c# 中有一个 maybe monad 的实现,它与您的略有不同,首先它与空值检查无关,我相信我的实现更接近于标准的 maybe 实现,例如 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;
        }
    }
}

正如您在此处看到的,NothingMaybe 的绑定(bind)函数仅返回一个新的空值,因此永远不会评估传入的联编程序表达式。从某种意义上说,它是短路的,一旦您进入“无状态”,将不会评估更多的 Binder 表达式,但是将为链中的每个 monad 调用 Bind 函数本身。

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

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); });

这会向控制台打印“whatever”,但是如果值为 null 或空,它什么也不做。

关于c# - Maybe monad 如何充当短路?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8278008/

相关文章:

c# - Folder.WellKnownFolderName 在 EWS 托管 API 和 Exchange 2013 中为空

process - 如何在 Idris 中调用子流程?

math - 哪些术语对应于类别理论中的 Map、Filter、Foldable、Bind 等?

scala - Scala 中 Option 的关联二进制操作

parsing - 如何在使用 Data.Aeson 解析 JSON 时正确出错

c# - 如何将此 VB 代码转换为 C#?

c# - 如何使用 Linq to SQL 投影到具有自定义类型的字典?

javascript - 区分数组、js中相同的值

scala - 处理 FP、IO Monad 中不纯的副作用

c# - 将动态创建的 lambda 应用于对象实例