8

モナドをより深く理解しようとしています。そのため、私は Maybe モナドを少し掘り下げ始めました。

私が正しくないように見えることが1つあります。これを読む:

「したがって、Maybe Bind は短絡回路として機能します。操作のチェーンで、いずれかが Nothing を返す場合、評価は中止され、チェーン全体から Nothing が返されます。」

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

この:

Maybe<T>タイプの場合、バインディングは単純なルールに従って実装されます。チェーンがある時点で空の値を返す場合、チェーンのそれ以降のステップは無視され、代わりに空の値が返されます」

From: 「C# での関数型プログラミング」http://www.amazon.com/Functional-Programming-Techniques-Projects-Programmer/dp/0470744588/

では、コードを見てみましょう。ここに私の多分モナドがあります:

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

そして、これがモナドを使った私のサンプルコードです:

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

ノード ツリーを可能な限り深く掘り下げようとしていることは明らかです。しかし、私が言及した引用によると、それがどのように機能しているかはわかりません。つまり、もちろん、null チェックを除外して、例が機能することを意味します。ただし、チェーンを早期に切断することはありません。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();
                      });
4

3 に答える 3

12

私はあなたのものとは少し異なるc#の多分モナドの実装を持っています。まず第一に、それは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;
        }
    }
}

ここでわかるように、NothingMaybe のバインド関数は新しい何も返さないため、バインダー式で渡されたものは評価されません。「何もない状態」になるとバインダー式が評価されなくなるという意味で短絡的ですが、バインド関数自体はチェーン内の各モナドに対して呼び出されます。

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 または空の場合は何もしません。

于 2011-11-26T11:20:55.347 に答える
5

私が理解しているように、すべてのBindメソッドが呼び出されますが、提供された式は前の式が値を返す場合にのみ評価されます。これは、返されるメソッド(より正確には: ) のBind後に呼び出されるメソッドは非常に安価であることを意味します。nulldefault(T)

于 2011-11-26T11:13:53.930 に答える