2010-12-13 22 views
2

C#でモナドを実装する最良の方法は何ですか?特定の実装戦略があるか、またはすべてのモナドが別の方法で実装されていますか?C#でモナドを実装する最良の方法は何ですか

+1

LINQ。 (例:http://lorgonblog.wordpress.com/2007/12/07/monadic-parser-combinators-a-noteworthy-aside/) – Brian

+0

このhttp://blogs.msdn.com/b/wesdyer/archive/ 2008/01/11/the-marvels-of-monads。aspxも素敵に見えます、悲しいかな、これは実装戦略の唯一の例です –

+0

Really LINQ(http://stackoverflow.com/questions/1418106/how-much-is-there-to-linq/1418176#1418176) – Dario

答えて

6

ちょうどそれにコメントするのではなく、質問に答えるために、おそらくLinqはC#でモナディック変換を実行する最前線の方法です。 Linqメソッド連鎖は、遅延評価され、順序付けされたリスト処理操作のセットにすぎません。

もちろん、Linqはロケット科学ではありません。あなたの平均的な大学レベルのプログラミングコース以上のものですが、まだまだです。これは一連の拡張メソッドであり、それぞれが実行すべきロジックを含むプレースホルダーの列挙型(モナド)とそのデータソース(別のモナドカプセル化でもよい)への参照を生成します。基本的なLinqライブラリに追加の拡張機能を追加したり、機能の穴を埋めたり、特定のニーズを満たすカスタムアクションを実行することができます。

また、実際に何かを行うために、独自のモナディックメソッド連鎖フレームワークを作成することもできます。 「流暢な」コーディングインターフェイスを持つと説明されているフレームワークやライブラリは、ほとんどがモナドベースのライブラリです。流暢なユニットテストアサーション、流暢なORM構成、さらには流暢なドメインUIマッピング拡張があります。

C#でモナドライブラリーを実装するのは、通常、静的クラスとスタティックメソッドを使用して行われます。静的メソッドは、意図した用途以外からはアクセスできない1つ以上のモナドタイプを使用します。例えば、ここでは整数の加算や減算を行い、基本的なモナドライブラリは次のとおりです。

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     int theValue; 

     internal Monad(int input) { theValue = input; } 

     public Monad Add(int input){ return new Monad(theValue + input); } 
     public Monad Subtract(int input){ return new Monad(theValue - result); } 
     public int Value { get { return theValue; } } 
    } 
} 

... 

//usage 
var result = MonadicArithmetic.Take(1).Add(2).Subtract(1).Value; //2 

明らかにこれは非常に基本的であり、すべての操作は、「熱心に」実行されます。具体的な値は、コードを消費することによって要求されるまで何も操作は行われませんを除いて、

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     //Change the "value keeper" into a Func that will return the value; 
     Func<int> theValue; 

     //the constructor now turns the input value into a lambda 
     internal Monad(int input) { theValue =()=>input; } 
     //and another constructor is added for intra-class use that takes a lambda 
     private Monad(Func<int> input) { theValue = input; } 

     //And now the methods will create new lambdas that call the existing lambdas 
     public Monad Add(int input){ return new Monad(()=>theValue() + input); } 
     public Monad Subtract(int input){ return new Monad(()=>theValue() - input); } 

     //Finally, our Value getter at the end will evaluate the lambda, unwrapping all the nested calls 
     public int Value { get { return theValue(); } } 
    } 
} 

同じ使用:

それらの操作が複雑であれば、これは最適ではないかもしれないので、代わりに、さんは「なまけ」、それらを実行してみましょう
//Each call just adds a shell to the nested lambdas 
var operation = MonadicArithmetic.Take(1).Add(2).Subtract(1); 

... 

//HERE's the payoff; the result is not evaluated till the call to Value_get() behind the scenes of this assignment. 
var result = operation.Value; 

ただし、これに問題があります。これらのメソッドは単に入力値を取り、ラムダでそれらを参照します。問題は、値のスコープが含まれるメソッド(ラムダが評価されるのに十分な長さではないことを意味する)に依存することです。 Value()ゲッターが呼び出されると、ラムダが評価され、これらの範囲外の変数がすべて参照されます。代わりに、私たちはラムダと同じくらい長く生きるものに値を残すべきです。モナドは明らかな選択肢です。考えられる解決策は次のとおりです。

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     //Our value keeper is now a pure function that requires no external closures 
     Func<Func<int>, int, int> operation; 
     //and we add two new private fields; 
     //a hook to a lambda that will give us the result of all previous operations, 
     Func<int> source; 
     //... and the value for the current operation. 
     private int addend; 

     //our constructor now takes the value, stores it, and creates a simple lambda 
     internal Monad(int input) { addend = input; operation =()=>addend; } 
     //and our private constructor now builds a new Monad from scratch 
     private Monad(Func<int> prevOp, Func<Func<int>, int, int> currOp, int input) 
     { 
      source = prevOp, 
      operation = currOp, 
      addend = input; 
     } 

     //The methods will create new Monads that take the current Monad's value getter, 
     //keeping the current Monad in memory. 
     public Monad Add(int input) 
     { 
     return new Monad(this.Result, (f,i)=>f()+i, input); 
     } 

     public Monad Subtract(int input) 
     { 
     return new Monad(this.Result, (f,i)=>f()-i, input); 
     } 

     //And we change our property to a method, so it can also 
     //be used internally as a delegate 
     public int Result() { return operation(source, addend); } 
    } 
} 

//usage 
var operations = MonadicArithmetic.Take(1).Add(3).Subtract(2); 
//There are now 3 Monads in memory, each holding a hook to the previous Monad, 
//the current addend, and a function to produce the result... 

... 

//so that here, all the necessary pieces are still available. 
var result = operations.Result(); 

これはモナディックライブラリの基本パターンです。全体を開始する静的メソッドは、Linqが使用するスタイルである拡張メソッドです。メソッドチェーンのルートが最初の値になる:

//using an "identity function" to convert to a monad 
var operations = 1.AsMonad().Add(2).Subtract(3); 

//performing the conversion implicitly from an overload of Add() 
var operations = 1.Add(2).Subtract(3); 

LINQの目的のために、そのライブラリはIEnumerableをを取るとのIEnumerableを返す拡張メソッドであることに特にエレガントなので、変換プロセスは、任意の過負荷なしで処理され、または明示的なメソッド呼び出し。しかし、翻訳可能な式ツリーを隠すIQueryableオブジェクトは.NET 3.5では新しいアイデアであり、AsQueryable()メソッドを使用して明示的にコレクションをIQueryableに変換する必要があります。

+0

乾杯実際の答え:)しかし、私はまだあなたの答えの戦略のいくつかのまっすぐな前方C#のコードや詳細を表示するに失敗します –

+3

私はあなたが何を記述しているとは思わないMonadの定義を満たしています。それはちょうど流暢なインターフェイスと思われる。 Monadsは、アイデンティティ、バインド&リターンといった特定の操作を実装する必要があります。 Linqはこれらの操作を実装しているのでモナドです。バインド操作は、例えば、 'SelectMany'拡張メソッドです。 – Enigmativity

+0

私は対立しています。 Wikipedia: "関数型プログラミングでは、モナドは、(ドメインモデルのデータではなく)計算を表現するために使用される抽象的なデータ型コンストラクタの一種で、パイプラインを構築するためにアクションを連鎖させることができます。モナドによって提供される追加の処理規則で装飾されています。 "これが成り立つならば、 "文法構造"(有効なメソッドを制御するために内部型の使用を必要とする)を持つ流暢なインターフェースは、モナドとしてラベル付けできます。この場合、私はモナド(Take)に変換し、変換(Add/Subtract)して返します。 – KeithS

関連する問題