この質問を投稿してから1年になります。投稿した後、数か月かけてHaskellについて詳しく調べました。ものすごく楽しかったですが、モナドを掘り下げる準備ができたのと同じように、脇に置いておきました。私は仕事に戻り、私のプロジェクトに必要なテクノロジーに集中しました。
これはかなりクールです。しかし、それは少し抽象的です。実際の例がないために、どのモナドがすでに混乱しているかを知らない人がいると想像できます。
それで、準拠するようにしましょう。見やすくするために、C#で例を示します。最後に同等のHaskellを追加して、IMO、モナドが本当に役立つようになる、かっこいいHaskell構文糖を示します。
わかりましたので、最も簡単なモナドの1つはHaskellで「Maybeモナド」と呼ばれています。C#ではMaybe型が呼び出されますNullable<T>
ます。これは基本的に、有効で値を持つ、または「null」で値を持たない値の概念をカプセル化する小さなクラスです。
このタイプの値を組み合わせるためにモナド内に固執するのに役立つのは、失敗の概念です。つまり、複数のnull許容値を調べてnull
、いずれか1つが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();
}
}
ここで、Nullable
C#でこれを実行するためのサポートがすでにあるため、しばらくは無視します(null許容のintを一緒に追加でき、どちらかがnullの場合はnullを取得します)。そのような機能はなく、特別な魔法のないユーザー定義クラスであるとしましょう。重要なのは、Bind
関数を使用して変数をNullable
値の内容にバインドし、何も変わっていないふりをして、それらを通常のintのように使用して、それらを加算するだけです。最後に結果をnullableでラップします。そのnullableはnullのいずれかであるか、nullを返すかf
、g
またはh
summingの結果になりますf
。g
およびh
一緒。(これは、データベースの行をLINQの変数にバインドし、それBind
を使用して、変数に有効な行の値のみが渡されることをオペレーターが確認するという点で安全な方法に似ています。)
あなたはこれで遊ぶとのいずれかを変更することができ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
(Nullable<A>
この場合のC#構文で)、およびから関数a
に
M b
(Func<A, Nullable<B>>
C#構文で)、それは返しM b
(Nullable<B>
)を。
コードは単にnullableに値が含まれているかどうかをチェックし、含まれている場合はそれを抽出して関数に渡し、それ以外の場合はnullを返します。これは、Bind
オペレーターがすべてのnullチェックロジックを処理することを意味します。呼び出すBind
値がnull以外の場合に限り、その値
はラムダ関数に「渡され」、それ以外の場合は早期に救済され、式全体がnullになります。これにより、モナドを使用して作成するコードにこのnullチェック動作を完全に適用せずBind
、モナディック値(fval
、
gval
およびhval
サンプルコード内)の値にバインドされた変数を使用して取得するだけで、それらを安全に使用できますBind
それらを渡す前に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つの関数を定義するだけで、基本的に独自の小さな言語を書くことができます。