たぶんステロイドのモナド

Habréのモナドについては非常に 多くの 出版物 がある ので 、もう1つ 欠け ている ように思えます。



モナドとは書きませんが、多分モナドの面白い実装を1つだけ示します(「異常なプログラミング」ハブにいますか?)。



このような単純なデリゲートを宣言しましょう。



public delegate T IMaybe<out T>();
      
      





ここで、このような単純な定義で本格的なオプション型を作成するのに十分であることを示します。



モナドには、ReturnとBindの2つのメソッドが必要です。 1つ目はモナドの非モナドの意味を「ラップ」し、2つ目は2つのモナド計算を接続できるようにします。



便宜上、静的クラスを作成し、必要なすべての関数の拡張機能をこのタイプに作成し、すべてのメソッドをそれに追加します。



 public static class Maybe { }
      
      





最初の関数-Return-は非常に簡単です。 値から、それを返すデリゲートを作成する必要があります。



 public static IMaybe<T> Return<T>(T x) { return () => x; }
      
      





また、価値の欠如の原因となる何かを宣言する必要があるかもしれません。 この場合、例外をスローするのはデリゲートです。



 public static IMaybe<T> Nothing<T>() { return () => { throw new InvalidOperationException("  "); }; }
      
      





モナドの2番目のメソッドであるバインドは、2つの計算をリンクする必要があります。 彼の署名:



 public static IMaybe<TResult> Bind<TArg, TResult>(this IMaybe<TArg> maybe, Func<TArg, IMaybe<TResult>> func)
      
      





彼にもっと近づきましょう。



実際、最初の引数は最初の単項の意味です。 2番目の引数は、モナド内の値から新しいモナド値を作成する関数です。 Bindメソッドの実装は、モナドから値を取得できるはずです。 この場合、値を取得するには、デリゲートを呼び出します。



 public static IMaybe<TResult> Bind<TArg, TResult>(this IMaybe<TArg> maybe, Func<TArg, IMaybe<TResult>> func) { return () => { var value = maybe(); var newMaybe = func(value); return newMaybe(); }; }
      
      





ここにいくつかのトリックがあります。 Bindメソッドには次のような実装があります。



 public static IMaybe<TResult> Bind<TArg, TResult>(this IMaybe<TArg> maybe, Func<TArg, IMaybe<TResult>> func) { //  ! return func(maybe()); }
      
      





ただし、キャッチがあります。 最初の引数としてNothingを渡すと、Bindメソッドは呼び出しの直後に例外をスローします。 ただしバインドではなく2つの計算をリンクする 必要があります。 したがって、バインドは、最初のモナドからの結果の受信を延期し、実際にモナドから値を計算するまで延期する必要があります。



たぶん、いくつかのメソッドを追加します:Select、Where、SelectMany



Selectメソッドは、Maybe内のオブジェクトに対して何らかの変換を実行します。 Bind and Returnを使用して実装できます。



 public static IMaybe<TResult> Select<TArg, TResult>(this IMaybe<TArg> maybe, Func<TArg, TResult> func) { return maybe.Bind(value => Return(func(value))); }
      
      







Maybe内で値をフィルタリングし、値が述語を満たさない場合はNothingを返します。



 public static IMaybe<T> Where<T>(this IMaybe<T> maybe, Func<T, bool> predicate) { return maybe.Bind(x => predicate(x) ? Return(x) : Nothing<T>()); }
      
      





SelectManyはBindに類似しており、Linq構文を使用して式を作成できます。 両方のモナドの値からの最終投影の存在により、単純なバインドとは異なります。



 public static IMaybe<TC> SelectMany<TA, TB, TC>(this IMaybe<TA> ma, Func<TA, IMaybe<TB>> maybeSelector, Func<TA, TB, TC> resultSelector) { return ma.Bind(a => maybeSelector(a).Select(b => resultSelector(a, b))); }
      
      





Select、Where、およびSelectManyメソッドがMaybeの内部構造について何も知らないことは注目に値します。Bind、Return、および空の値(たぶんNothing)のみを使用します。 おそらく別の実装に置き換えることができます-これらのメソッドは変更されません。 さらに、リストなどの別のモナドに置き換えることもできます。



 public static IEnumerable<T> Return<T>(T x) { return new[] { x }; } public static IEnumerable<T> Nothing<T>() { yield break; } public static IEnumerable<TResult> Bind<TArg, TResult>(this IEnumerable<TArg> m, Func<TArg, IEnumerable<TResult>> func) { foreach (var arg in m) { foreach (var result in func(arg)) { yield return result; } } }
      
      





...また、これらの方法は同じままです。 型クラスがある場合、これらのメソッドをMonad型クラスで宣言します(Haskellでこれを行う方法*)(実際にはそうではありません)。



最後に残っているのは、実際にMaybeを使用していることです。



 var one = Maybe.Return(1); var nothing = Maybe.Nothing<int>(); var nothing2 = from ax in one from ay in nothing select ax + ay; var two = one.Where(z => z > 0).Select(z => z + 1); Console.WriteLine(one()); Console.WriteLine(two()); Console.WriteLine(nothing2());
      
      





最後の3行で発生するデリゲートを呼び出す以外に、モナドから値を取得する方法はありません。 最後の行は、「値を取得できません」という例外を除いて表示されます。



ここではすべて一緒に見ることができます



All Articles