17

私は。。をしようとしていますgrokモナドの予備的な理解を得る。

データ層の呼び出しがあり、その結果を、更新された行やデータセットなどの結果がないか、例外の結果として一元的に返したい場合があります。どちらかのモナドの特殊なケースとして見ることができる例外モナドを使用する必要があると思います

私はさまざまなサンプル(多分サンプルのトン)を調べましたが、これを一般化してどちらかのモナドにするかどうかはよくわかりませんが、haskellにないものは見つかりません-残念ながら、私は間違いなく、Haskellを悪用しないでください!

誰かが私にサンプルを教えてもらえないかと思っていました。

4

5 に答える 5

16

C#でモナドについて少し学びながら、演習のExceptionalために自分でモナドを実装しました。このモナドを使用するExceptionと、次の2つの例のようなものをスローする可能性のある操作を連鎖させることができます。

var exc1 = from x in 0.ToExceptional()
           from y in Exceptional.Execute(() => 6 / x)
           from z in 7.ToExceptional()
           select x + y + z;
Console.WriteLine("Exceptional Result 1: " + exc1);

var exc2 = Exceptional.From(0)
           .ThenExecute(x => x + 6 / x)
           .ThenExecute(y => y + 7);
Console.WriteLine("Exceptional Result 2: " + exc2);

どちらの式も同じ結果になりますが、構文が異なります。結果はExceptional<T>、発生したDivideByZeroExceptionセットをプロパティとして持つことになります。最初の例は、LINQを使用したモナドの「コア」を示し、2番目の例には、より理解しやすい方法でメソッドチェーンを示す、異なる、おそらくより読みやすい構文が含まれています。

それで、それはどのように実装されますか?Exceptional<T>タイプは次のとおりです。

public class Exceptional<T>
{
    public bool HasException { get; private set; }
    public Exception Exception { get; private set; }
    public T Value { get; private set; }

    public Exceptional(T value)
    {
        HasException = false;
        Value = value;
    }

    public Exceptional(Exception exception)
    {
        HasException = true;
        Exception = exception;
    }

    public Exceptional(Func<T> getValue)
    {
        try
        {
            Value = getValue();
            HasException = false;
        }
        catch (Exception exc)
        {
            Exception = exc;
            HasException = true;
        }
    }

    public override string ToString()
    {
        return (this.HasException ? Exception.GetType().Name : ((Value != null) ? Value.ToString() : "null"));
    }
}

モナドは、モナドのUnit関数とBind関数に対応する拡張メソッドToExceptional<T>()とによって完成されます。SelectMany<T, U>()

public static class ExceptionalMonadExtensions
{
    public static Exceptional<T> ToExceptional<T>(this T value)
    {
        return new Exceptional<T>(value);
    }

    public static Exceptional<T> ToExceptional<T>(this Func<T> getValue)
    {
        return new Exceptional<T>(getValue);
    }

    public static Exceptional<U> SelectMany<T, U>(this Exceptional<T> value, Func<T, Exceptional<U>> k)
    {
        return (value.HasException)
            ? new Exceptional<U>(value.Exception)
            : k(value.Value);
    }

    public static Exceptional<V> SelectMany<T, U, V>(this Exceptional<T> value, Func<T, Exceptional<U>> k, Func<T, U, V> m)
    {
        return value.SelectMany(t => k(t).SelectMany(u => m(t, u).ToExceptional()));
    }
}

そして、モナドのコアの一部ではないいくつかの小さなヘルパー構造:

public static class Exceptional
{
    public static Exceptional<T> From<T>(T value)
    {
        return value.ToExceptional();
    }

    public static Exceptional<T> Execute<T>(Func<T> getValue)
    {
        return getValue.ToExceptional();
    }
}

public static class ExceptionalExtensions
{
    public static Exceptional<U> ThenExecute<T, U>(this Exceptional<T> value, Func<T, U> getValue)
    {
        return value.SelectMany(x => Exceptional.Execute(() => getValue(x)));
    }
}

説明:このモナドで構築されたメソッドチェーンは、チェーンの1つのメソッドが例外をスローする限り実行されます。この場合、チェーンのこれ以上のメソッドは実行されず、最初にスローされた例外がExceptional<T>結果の一部として返されます。この場合、HasExceptionおよびExceptionプロパティが設定されます。Exception発生しない場合は、実行HasExceptionされfalseValueメソッドチェーンの結果を含むプロパティが設定されます。

Exceptional<T>(Func<T> getValue)コンストラクターは例外処理を担当し、SelectMany<T,U>()メソッドは以前に実行されたメソッドが例外をスローしたかどうかを区別する責任があることに注意してください。

于 2012-06-27T07:33:50.553 に答える
4

だから - 誰かが興味を持っているかどうかわからない - 私は非常に 予備的な実装を思いついた. それのいくつかは、まったく適切な気分ではありませんが、それは始まりです. (そうは言っても、私はそれを使用しません-100万ドルを失うか、誰かを殺すことさえあるかもしれません-私の警告です!)

記述できるコードの種類の非常に単純なサンプルは次のとおりです。

var exp = from a in 12.Div(2)
          from b in a.Div(2)
          select a + b;

Assert.AreEqual(9, exp.Value());

var exp = from a in 12.Div(0)
          from b in a.Div(2)
          select b;

Assert.IsTrue(exp.IsException());

次のように実装された Div メソッドを使用します。

public static IExceptional<int> Div(this int numerator, int denominator)
{
    return denominator == 0
           ? new DivideByZeroException().ToExceptional<int, DivideByZeroException>()
           : (numerator / denominator).ToExceptional();
}

また

public static IExceptional<int> Div_Throw(this int numerator, int denominator)
{
    try
    {
        return (numerator / denominator).ToExceptional();
    }
    catch (DivideByZeroException e)
    {
        return e.ToExceptional<int, DivideByZeroException>();
    }            
 }

(すぐに、API の改善の可能性を確認できますが、それを達成する方法がまったくわかりません。これは

new DivideByZeroException().ToExceptional<int, DivideByZeroException>()

だったらもっといいのに

new DivideByZeroException().ToExceptional<int>()

後で私の実装が表示されます。うまくいけば、誰かが上記のために再構築できるでしょう。)

モナドビットはここで行われます(主に)

public static class Exceptional
{
    public static IExceptional<TValue> ToExceptional<TValue>(this TValue result)
    {
        return new Value<TValue>(result);
    }

    public static IExceptional<TValue> ToExceptional<TValue,TException>(this TException exception) where TException : System.Exception
    {
        return new Exception<TValue, TException>(exception);
    }


    public static IExceptional<TResultOut> Bind<TResultIn, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultOut>> func)
    {                
        return first.IsException() ? 
                ((IInternalException)first).Copy<TResultOut>() : 
                func(first.Value());
    }


    public static IExceptional<TResultOut> SelectMany<TResultIn, TResultBind, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultBind>> func, Func<TResultIn, TResultBind, TResultOut> select)
    {
        return first.Bind(aval => func(aval)
                    .Bind(bval => select(aval, bval)
                    .ToExceptional()));
    }   
}

メインインターフェイスは次のように指定されています

public interface IExceptional<TValue>
{
    bool IsException();    
    TValue Value();
}

そして、スローされた例外を取得するために使用する内部インターフェースがあります(後で詳しく説明します)

internal interface IInternalException 
{
    IExceptional<TValue> Copy<TValue>();     
}

具体的な実装は次のとおりです。

public class Value<TValue> : IExceptional<TValue>
{
    TValue _value = default(TValue);

    public Value(TValue value)
    {
        _value = value;
    }

    bool IExceptional<TValue>.IsException()
    {
        return false;
    }

    TValue IExceptional<TValue>.Value()
    {
        return _value;
    }
}

public class Exception<TValue, TException> : IInternalException, IExceptional<TValue> where TException : System.Exception
{
    TException _exception = default(TException);

    public Exception(TException exception)
    {
        _exception = exception;
    }

    bool IExceptional<TValue>.IsException()
    {
        return true;
    }

    IExceptional<TOutValue> IInternalException.Copy<TOutValue>()
    {
        return _exception.ToExceptional<TOutValue,TException>();
    }

    TException GetException()
    {
        return _exception;
    }

    TValue IExceptional<TValue>.Value()
    {
        return default(TValue);
    }
}

説明の言葉...私にとって最もトリッキーなビットは、例外が発生したときのバインド操作でした。操作のパイプラインを扱っていて、プロセスの早い段階で例外がスローされた場合は、その例外をパイプラインに永続化して、式が完了すると返される IExceptional に以前に発生した例外が含まれるようにする必要があります。これが IInternalException の理由です。これにより、同じまたは (潜在的に異なる) タイプの新しい IExceptional を作成できます (例: IExceptional --> IExceptional) が、内部例外のタイプを知らなくても、例外を新しい IExceptional にコピーします。

間違いなく、多くの改善が可能です。たとえば、IExceptional 内のエラー スタックを追跡したい可能性があることがわかりました。おそらく、目的を達成するための冗長なコードまたはより良い方法があります...しかし...それは私にとって少し学習することを意図していました。

どんな考えや提案もありがたく受け取られます。

于 2012-05-29T06:45:56.540 に答える
0

C# はモナドをあまりサポートしていません (そして、LINQ の形式で提供されているサポートは、実際には一般的なモナドを対象としたものではありませんでした)。組み込みの Exception モナドや、Either モナドはありません。あなたはthrow例外でなければなりませんcatch

于 2012-05-27T09:06:30.287 に答える