この質問を投稿してから1年が経ちました。それを投稿した後、私は Haskell を数か月掘り下げました。私はそれを大いに楽しんだが、モナドを掘り下げる準備ができたので、それを脇に置いた。私は仕事に戻り、プロジェクトに必要なテクノロジーに集中しました。
そして昨夜、私は来て、これらの回答を読み直しました。最も重要なことは、誰かが上で言及した Brian Beckman のビデオのテキスト コメントにある特定の C# の例を読み直したということです。それは完全に明確で明快だったので、ここに直接投稿することにしました.
このコメントのおかげで、モナドが何であるかを正確に理解しているように感じるだけでなく、モナドである何かを C# で実際に書いたことがあることに気付きました…または少なくとも非常に近く、同じ問題を解決しようと努力しています。
それで、ここにコメントがあります – これはすべて、sylvanによるコメントからの直接の引用です。
これはかなりクールです。少し抽象的ですが。実際の例がないため、モナドが何であるかを知らない人はすでに混乱していると想像できます。
ですから、従うようにしましょう。明確にするために、C# で例を示しますが、見栄えは悪くなります。同等の Haskell を最後に追加し、モナドが本当に便利になり始めるクールな Haskell シンタックス シュガーを紹介します。
さて、最も簡単なモナドの 1 つは、Haskell では「Maybe モナド」と呼ばれます。C# では、Maybe 型は と呼ばれNullable<T>
ます。これは基本的に、有効で値を持つ値、または「null」で値を持たない値の概念をカプセル化するだけの小さなクラスです。
この型の値を結合するためにモナド内に固執するのに役立つのは、失敗の概念です。つまり、複数の null 許容値を見てnull
、それらのいずれかが null になるとすぐに戻ることができるようにしたいのです。これは、たとえば、辞書などで多くのキーを検索し、最後にすべての結果を処理して何らかの方法で結合したい場合に役立ちますが、いずれかのキーが辞書にない場合、あなたnull
はすべてのために戻りたいです。ルックアップとリターンのそれぞれを手動でチェックしなければならないのは面倒な
null
ので、このチェックを bind 演算子の内部に隠すことができます (これは一種のモナドのポイントです。bind 演算子に簿記を隠して、コードを簡単に実行できるようにします)。詳細を忘れることができるので使用してください)。
これが、全体の動機となるプログラムです (後で定義します
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();
}
}
ここで、Nullable
C# でこれを行うためのサポートが既にあることをしばらく無視してください (null 許容 int を一緒に追加すると、どちらかが null の場合は null になります)。そのような機能はなく、特別な魔法のない単なるユーザー定義クラスであるとしましょう。要点は、関数を使用しBind
て変数を値の内容にバインドし、Nullable
何も変わっていないふりをして、通常の int のように使用し、それらを足し合わせることができるということです。結果を最後に nullable でラップします。その nullable は null ( の場合f
、g
またはnull を返す) か、 、、およびh
を合計した結果になります。f
g
h
一緒。(これは、データベース内の行を LINQ 内の変数にバインドし、それを処理する方法に似ています。Bind
オペレーターが変数に有効な行値のみが渡されることを確認することを知っていれば安全です)。
これで遊んでf
、 、g
、およびのいずれかを変更h
して null を返すと、全体が 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;
}
ここのタイプはビデオのようです。(この場合の C# 構文では) と関数 from to
( C# 構文では)を取り、M a
(
)を返します。Nullable<A>
a
M b
Func<A, Nullable<B>>
M b
Nullable<B>
このコードは、nullable に値が含まれているかどうかを単純にチェックし、含まれている場合はそれを抽出して関数に渡し、そうでない場合は単に null を返します。これは、Bind
オペレータがすべての null チェック ロジックを処理することを意味します。呼び出す値が null でない場合にのみ、その値
Bind
はラムダ関数に「渡され」ます。それ以外の場合は、早期に救済され、式全体が null になります。これにより、モナドを使用して記述したコードは、この null チェック動作から完全に解放されますBind
。モナド値内の値にバインドされた変数を使用して取得するだけで (コード例ではfval
,
)、それらを安全に使用できます。それらを渡す前に null のチェックを処理する知識の中で。gval
hval
Bind
モナドでできることの例は他にもあります。たとえば、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 など) ですべての有用なものをカプセル化するために使用でき、この素晴らしい命令型のような構文を使用して使用できますが、カーテンの後ろでは、すべてモナドであり、bind 演算子の巧妙な実装です! クールなのは、 and を実装することで独自のモナドを実装できること>>=
ですreturn
。そうすれば、それらのモナドもdo
表記法を使用できるようになります。つまり、基本的には、2 つの関数を定義するだけで、独自の小さな言語を作成できます。