2009-03-23 15 views
177

最近、モナドについて多くの話があります。私はいくつかの記事/ブログ記事を読んだことがありますが、そのコンセプトを完全に理解するためには、その例を十分に読むことはできません。その理由は、モナドは機能的な言語のコンセプトなので、私が扱っていない言語での例です(機能的な言語を使用していないためです)。私は文章を十分に深く理解することはできません...しかし、私はそこに理解できる価値があると言うことができます。C#開発者がモナドとは何かを理解するのに役立ちますか?

しかし、私はラムダ式やその他の機能を含めてC#をよく理解しています。私はC#が機能的な機能のサブセットしか持っていないことを知っているので、おそらくモナドはC#で表現できません。

しかし、確かにコンセプトを伝えることは可能ですか?少なくとも私はそう願っています。たぶん、C#の例を基盤として提示し、C#開発者がを希望していることを記述することができます。はそこから行うことができますが、言語に関数型プログラミングの機能がないためできません。これは、モナドの意図と利点を伝えるため、素晴らしいでしょう。だから私の質問です:あなたはC#3の開発者にモナドを与えることができる最良の説明は何ですか?

ありがとうございました!

(編集:ちなみに、私は少なくとも3つの "モナドは何ですか?"という質問が既にあることを知っていますが、私は同じ問題に直面しています...ありがとう)。

+0

実際はC#3.0の開発者です。 .NET 3.5と間違えないでください。それ以外に、すてきな質問。 – Razzie

+3

LINQクエリ式はC#3のモナドの動作の例であることを指摘しておきましょう。 –

+1

私はまだ重複した質問だと思っています。 http://stackoverflow.com/questions/2366/can-anyone-explain-monadsの回答の1つはhttp://channel9vip.orcsweb.com/shows/Going+Deep/Brian-Beckman-Dont-fear-にリンクしています。 the-Monads /、コメントの1つが非常に素晴らしいC#の例を持っています。 :) – jalf

答えて

9

モナドは本質的に遅延処理です。副作用(例:I/O)を許さない言語でコードを記述しようとしていて、純粋な計算しかできない場合は、「Ok、副作用はしないでしょう私のために、あなたがしたらどうなるか計算してもらえますか? "

これは不正行為です。

この説明は、モナドの全体的な意図を理解するのに役立ちますが、悪魔は詳細です。どのくらい正確にあなたは結果を計算しますか?時々、それはかわいくない。

命令型プログラミングに慣れてきた人の概観を示す最も良い方法は、モナドの外に慣れ親しんだような構文的に見える操作が代わりにDSLを構築するDSLに入れていると言うことです。あなたが(例えば)出力ファイルに書き込むことができるなら、あなたが望むことをする関数です。あなたが後でevalされるように文字列にコードを構築していたかのように(しかし、実際はそうではありません)ほとんど。

+1

Iロボットの本のように?科学者がコンピュータに宇宙旅行を計算し、特定の規則を飛ばしてもらうよう依頼するところ? ::) :) :) – OscarRyz

+3

Hmm、Monadは、遅延処理と副作用関数をカプセル化するために使用できます。実際はHaskellでの最初の実際のアプリケーションでしたが、実際ははるかに一般的なパターンです。他の一般的な用途には、エラー処理と状態管理が含まれます。構文的な砂糖(ハスケル、F#の計算式、C#のLinq構文)は単なるモナドの基本的なものです。 –

+0

@MikeHadlow:エラー処理のためのモナドインスタンス( 'Maybe'と 'Either e')と状態管理(' State s'、 'ST s')は、私のための副作用] "。別の例は、非決定論( '[]')です。 – pyon

4

他のユーザーが投稿してもらえると確信していますが、this videoがある程度役立っていますが、私はまだコンセプトが流暢ではないと言います)Monadsと直感的に問題を解決し始める。

+1

私がもっと助けてくれたのは、ビデオの下にC#の例を含むコメントでした。 – jalf

+0

私はあまり役に立たないが、確かにそのアイディアを実践している。 – TheMissingLINQ

0

モナドはa C# interface that classes have to implementと考えることができます。これは実用的な答えです。なぜなら、これらの宣言をあなたのインターフェースに持つことを選択したい理由の背後にあるすべてのカテゴリの理論的な数学を無視し、モナドを副作用を回避しようとする言語にしたい理由をすべて無視するからです。私はそれを理解している人(C#)のインターフェースとしては良いスタートだと感じました。

+0

詳しいことはできますか?それをモナドに関連付けるインターフェースについてはどうですか? –

+1

私はブログの記事がその質問に捧げられたいくつかの段落を費やすと思います。 – hao

141

あなたが終日プログラミングすることの大部分は、いくつかの機能を組み合わせて、より大きな機能を構築することです。通常、ツールボックスには関数だけでなく、演算子や変数の代入なども含まれますが、一般的に、プログラムはより多くの "計算"を組み合わせて、さらに大きな計算に結合します。

モナドは、この「計算の結合」のやり方です。

通常2つの計算を組み合わせるためのあなたの最も基本的な「演算子は」;です:

a; b 

あなたがこれを言うとき、あなたが意味「最初bを行い、その後、aを行います」。結果a; bは、基本的には、より多くのものと組み合わせることができる計算です。 これはシンプルなモナドです。小さな計算を大きなものに合わせる方法です。 ;は、 "左のものをやる、次に右のものをやる"と言う。

オブジェクト指向言語でモナドと見ることができる別のものは.です。多くの場合、あなたはこのようなものを見つける:

a.b().c().d() 

.は、基本的には、「左の計算を評価し、その結果に右側のメソッドを呼び出す」を意味します。関数/計算を組み合わせる別の方法ですが、;より少し複雑です。 .と一緒に物事を連想させるという概念は、2つの計算を一緒に新しい計算に組み合わせる方法なので、モナドです。特別な構文を持っていない

もうかなり一般的なモナドは、このパターンです:場合でも、-1

rv = socket.bind(address, port); 
if (rv == -1) 
    return -1; 

rv = socket.connect(...); 
if (rv == -1) 
    return -1; 

rv = socket.send(...); 
if (rv == -1) 
    return -1; 

戻り値は失敗を示しますが、アウト抽象、このエラーチェックへの現実的な方法はありませんこのようにして結合する必要のある多くのAPIコールがあります。これは、基本的には、関数呼び出しを "左の関数が-1を返した場合は-1を返し、そうでなければ右の関数を呼び出す"というルールによる関数を組み合わせたモナドです。私たちはこの事をしたオペレータ>>=を持っていた場合、我々は単に書くことができます:

socket.bind(...) >>= socket.connect(...) >>= socket.send(...) 
我々は自分自身を繰り返す必要がないように、それは、物事をより読みやすくするとの組み合わせの機能のうち、抽象私たちの特別な方法に役立つだろう

何度も何度も繰り返します。

一般的なパターンとして有用な関数/計算を結合してモナドで抽象化することができる多くの方法があります。モナドのユーザーはより簡潔で明確なコードを書くことができます。使用された機能の維持および管理はモナドで行われる。

:私たちは、明示的に倍の socket多くを指定する必要はありませんように、上記 >>=は、「エラーチェックを行い、その後、我々は、入力として得たソケット上で右サイドを呼び出す」するように拡張することができ例えば

new socket() >>= bind(...) >>= connect(...) >>= send(...); 

正式な定義は、その関数がその入力を必要とする場合、次の関数への入力として1つの関数の結果を取得する方法を心配する必要があるため、もう少し複雑です。あなたがモナドでそれらを組み合わせようとする方法に合わせて組み合わせる機能があります。しかし基本的なコンセプトは、関数を組み合わせるさまざまな方法を形式化することです。

+26

偉大な答え!私はOliver Steeleの引用を投げて、MonadsをC++やC#のオーバーロードに関連づけようとしています:Monadsはあなたに ';'オペレーター。 –

+5

@JörgWMittag私は前にその引用文を読んだが、それはあまりにも頭のいいナンセンスのように聞こえた。今私はモナドを理解し、この説明を読んで ';' 1つ、私はそれを得る。しかし、私はそれが、ほとんどの命令開発者にとっては、実際には非合理的な発言だと思います。 ';' //これ以上の演算子ではありません。 –

+0

モナドは何ですか? Monadsは「関数」や計算ではなく、モナドの規則があります。 – Luis

41

この質問を投稿してから1年が経過しています。それを掲示した後、私は数カ月間ハスケルに掘り下げた。私はそれをすごく楽しんだが、私はMonadsに掘り下げる準備ができていたときと同じようにそれを置いた。私は仕事に戻り、プロジェクトに必要な技術に焦点を当てました。

そして最後の夜、私は来て、これらの反応を再読しました。 最も重要なのはです。私は、the Brian Beckman video誰かmentions aboveのテキストコメントでthe specific C# exampleを再読しました。それは私がここにそれを直接掲示することを決めたように、とても完全に明確で明るいものでした。私は正確を理解して好きなので、このコメントを、私は...私はモナド...または少なくとも非常に接近していることを私は実際にC#でいくつかのことを書いて実現する、とに努力しているものモナド

を感じていないだけ同じ問題を解決する。

だから、ここのコメントがあります - これはsylvanによってthe comment hereからのすべての直接の引用です:

これはかなりクールです。それは少し抽象です。実際の事例がないために、どのモナドがすでに混乱しているのか分からない人がいると思います。

私が遵守しようとすると、実際にはっきりするように、私はC#で例を挙げます。私は終わりに相当するハスケルを追加し、あなたには、IMOのモナドが実際に役に立つようになる、涼しいハスケルの構文的な砂糖を示します。

ハスケルでもっとも簡単なモナドの1つは、「多分モナド」と呼ばれています。 C#では、MaybeタイプはNullable<T>と呼ばれています。これは、基本的には、有効で値があるか、または「null」で値がない値の概念をカプセル化したちょっとしたクラスです。

このタイプの値を組み合わせるためのモナドの中にスティックするのに便利なのは、失敗の概念です。私。複数のnull可能な値を見て、いずれか1つがnullになるとすぐにnullを返したいと考えています。たとえば、辞書などの中でたくさんのキーを検索し、最後にすべての結果を処理して何らかの方法で結合したい場合に便利ですが、いずれかのキーが辞書にない場合は、あなたは全体のためにnullを返すしたいと思います。これは、手動でnullとリターンのために、それぞれのルックアップをチェックする必要が面倒になるので、私たちはモナドのポイントの一種である(バインド演算子内でこのチェックを非表示にすることができ、我々は、コードが容易になり、バインドオペレータで記帳を隠します私たちは細部について忘れることができるので使用する)。

これはすべてのことを動機づけするプログラムです(後ほどBindを定義します。なぜこれが良いのかを示すためです)。

class Program 
    { 
     static Nullable<int> f(){ return 4; }   
     static Nullable<int> g(){ return 7; } 
     static Nullable<int> h(){ return 9; } 


     static void Main(string[] args) 
     { 


      Nullable<int> z = 
           f().Bind(fval => 
           g().Bind(gval => 
           h().Bind(hval => 
           new Nullable<int>(fval + gval + hval)))); 

      Console.WriteLine(
        "z = {0}", z.HasValue ? z.Value.ToString() : "null"); 
      Console.WriteLine("Press any key to continue..."); 
      Console.ReadKey(); 
     } 
    } 

さて、すでにC#でNullableためにこれを行うためのサポートは、(あなたが一緒にNULL可能int型を追加することができますし、どちらかがnullの場合はnullを取得)があることを一瞬無視。そのような機能はないと推測してみましょう。特別な魔法を持たないユーザー定義のクラスです。ポイントは、Bind関数を使用して、変数をNullable値の内容にバインドしてから、何も変わっていないふりをして、通常のintのように使用してそれらを一緒に追加するということです。最後にnullableに結果をラップし、nullable(f,gまたはhのいずれかがnullを返す場合)またはf,g、およびhを合計した結果になります。 (これはBindオペレータは、変数にのみ、これまで有効な行の値を渡されることを確認しますことを、我々はLINQでの変数にデータベース内の行を結合し、そしてそれをものを行うことができる方法の類似した知識で安全です)。

f,g、およびhのいずれかをnullに戻すと、すべてがnullを返すことがわかります。

明らかに、バインド演算子がこのチェックを行う必要があり、ヌル値に遭遇した場合はnullを返し、それ以外の場合はNullable構造体内の値をラムダに渡します。

はここBindオペレータです:ここ

public static Nullable<B> Bind<A,B>(this Nullable<A> a, Func<A,Nullable<B>> f) 
    where B : struct 
    where A : struct 
{ 
    return a.HasValue ? f(a.Value) : null; 
} 

種類はちょうどビデオのようです。 M a(この場合はC#の構文ではNullable<A>)、関数はaからM b(C#の構文ではFunc<A, Nullable<B>>)です。M bNullable<B>)を返します。

コードでは、nullableに値が含まれているかどうかを確認し、値があればそれを抽出して関数に渡します。それ以外の場合はnullを返します。つまり、Bind演算子がすべてのヌルチェックロジックを処理します。 Bindという値がnullでない場合に限り、その値はラムダ関数に渡されます。そうでない場合、早期にベールアウトされ、式全体がnullになります。 これは、モナドを使って書いたコードがこのヌルチェックの振る舞いから完全に自由になることを可能にします。ちょうどBindを使用し、モナド値(例コードではfval,gvalおよびhvalBindはそれらを渡す前にnullをチェックする世話をします。

モナドでできることの他の例があります。たとえば、Bindオペレータに入力文字ストリームを処理させ、それをパーサコンビネータを書き込むために使用することができます。それぞれのパーサーコンビネータは、バックトラッキング、パーサの失敗などのことを完全に無視することができます。ちょうど間違っていないかのように、小さなパーサーを組み合わせるだけです。Bindの賢明な実装では、困難なビット。その後、誰かがモナドにログを追加するかもしれないが、モナドを使用しているコードは変更されない。なぜなら、すべての魔法はBindオペレータの定義で起こり、コードの残りの部分は変更されないからである。

最後に、Haskellで同じコードを実装しています(--はコメント行を開始します)。

-- Here's the data type, it's either nothing, or "Just" a value 
-- this is in the standard library 
data Maybe a = Nothing | Just a 

-- The bind operator for Nothing 
Nothing >>= f = Nothing 
-- The bind operator for Just x 
Just x >>= f = f x 

-- the "unit", called "return" 
return = Just 

-- The sample code using the lambda syntax 
-- that Brian showed 
z = f >>= (\fval -> 
    g >>= (\gval -> 
    h >>= (\hval -> return (fval+gval+hval)))) 

-- The following is exactly the same as the three lines above 
z2 = do 
    fval <- f 
    gval <- g 
    hval <- h 
    return (fval+gval+hval) 

あなたが最後に素敵なdo表記を見ることができるように、それがまっすぐに不可欠のコードのように見えます。実際、これは設計によるものです。モナドは、命令プログラミング(可変状態、IOなど)のすべての有用なものをカプセル化するために使用でき、この素敵な命令的な構文を使用していますが、カーテンの後ろにはすべてモナドだけでなく、バ​​インド演算子の巧妙な実装です! クールなことは、>>=returnを実装して、独自のモナドを実装できることです。そしてもしそうすれば、それらのモナドはdo表記を使うことができます。つまり、基本的に2つの関数を定義するだけで、あなた自身の小さな言語を書くことができます!

+2

個人的に私はF#のモナドの方が好きですが、どちらの場合でも素晴らしいです。 – ChaosPandion

+3

ここに戻って投稿を更新していただきありがとうございます。このようなフォローアップは、特定の分野を熟考しているプログラマーが、単に「どのように私がxテクノロジをやるのか」ということから、仲間のプログラマーが最終的にはその領域をどのように見ているかを本当に理解するのに役立ちます。あなたダ男! – kappasims

+0

私はあなたが基本的に持っているのと同じ道をとって、モナドを理解している同じ場所に到着しました。これは、命令的な開発者にとって今まで見たモナドの束縛行動の単なる最善の説明だと言いました。私はあなたがsthによってもう少し詳しく説明されているモナドに関するすべてには触れていないと思うが。 –

0

私のanswerを参照してください。「モナドとは?「それは動機の例で始まる

は、例を介して動作、モナドの例を導出し、正式に定義する 『モナド』を。

これは、関数型プログラミングの知識を負いませんし、それが持つfunction(argument) := expression構文で擬似コードを使用しています最も単純な式

このC#プログラムは、擬似コードモナドの実装である。。(参考:M型コンストラクタであり、feedは「バインド」操作であり、wrapは、「戻る」操作である。)

using System.IO; 
using System; 

class Program 
{ 
    public class M<A> 
    { 
     public A val; 
     public string messages; 
    } 

    public static M<B> feed<A, B>(Func<A, M<B>> f, M<A> x) 
    { 
     M<B> m = f(x.val); 
     m.messages = x.messages + m.messages; 
     return m; 
    } 

    public static M<A> wrap<A>(A x) 
    { 
     M<A> m = new M<A>(); 
     m.val = x; 
     m.messages = ""; 
     return m; 
    } 

    public class T {}; 
    public class U {}; 
    public class V {}; 

    public static M<U> g(V x) 
    { 
     M<U> m = new M<U>(); 
     m.messages = "called g.\n"; 
     return m; 
    } 

    public static M<T> f(U x) 
    { 
     M<T> m = new M<T>(); 
     m.messages = "called f.\n"; 
     return m; 
    } 

    static void Main() 
    { 
     V x = new V(); 
     M<T> m = feed<U, T>(f, feed(g, wrap<V>(x))); 
     Console.Write(m.messages); 
    } 
} 
関連する問題