だから - 誰かが興味を持っているかどうかわからない - 私は非常に 予備的な実装を思いついた. それのいくつかは、まったく適切な気分ではありませんが、それは始まりです. (そうは言っても、私はそれを使用しません-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 内のエラー スタックを追跡したい可能性があることがわかりました。おそらく、目的を達成するための冗長なコードまたはより良い方法があります...しかし...それは私にとって少し学習することを意図していました。
どんな考えや提案もありがたく受け取られます。