モナドとは、コンテキスト (m と呼びます) を取り、モナドの法則を尊重しながら値を「生成」する「もの」です。これは、モナドの「内側」と「外側」という観点から考えることができます。モナドの法則は、「往復」に対処する方法を教えてくれます。特に、法則は、m (ma) が本質的に (ma) と同じ型であることを示しています。
ポイントは、モナドはこのラウンドトリップの一般化であるということです。join は (m (ma)) を (ma) に押しつぶし、(>>=) はモナドから値を引き出し、関数をモナドに適用します。別の言い方をすれば、関数 (f :: a -> mb) を (ma) の a に適用し、(m (mb)) を生成し、join を介してそれを押しつぶして (mb) を取得します。
では、これは「get」とオブジェクトとどのような関係があるのでしょうか?
do 記法は、計算の結果がモナドにあるように設定します。そして (<-)はモナドから値を引き出すことを可能にするので、名目上はモナドの中にいながら関数にバインドすることができます。たとえば、次のようになります。
doStuff = do
a <- get
b <- get
return $ (a + b)
a と b は純粋であることに注意してください。get の「外側」にあるのは、実際に get の内側を覗いたからです。しかし、モナドの外に値があるので、それを使って (+) 何かをしてからモナドに戻す必要があります。
これはほんの少し示唆に富む表記法ですが、次のようにするとよいでしょう。
doStuff = do
a <- get
b <- get
(a + b) -> (\x -> return x)
その前後を本当に強調します。モナド アクションを終了すると、そのテーブルの右側の列にいる必要があります。これは、アクションが完了すると、レイヤーを平坦化するために「結合」が呼び出されるためです。(少なくとも、概念的には)
ああ、オブジェクト。ええ、明らかに、オブジェクト指向言語は基本的に、ある種の IO モナドの中で生活し、呼吸しています。しかし、実際にはもう少し分解することができます。次の行に沿って何かを実行すると:
x = foo.bar.baz.bin()
あなたは基本的にモナドトランスフォーマースタックを実行しています。これはIOコンテキストを取り、fooコンテキストを生成し、barコンテキストを生成し、bazコンテキストを生成し、binコンテキストを生成します。そして、ランタイム システムは、必要に応じて何度でも join を「呼び出し」ます。このアイデアが「コール スタック」とうまくかみ合っていることに注目してください。実際、これが Haskell 側で「モナド変換スタック」と呼ばれる理由です。これは、モナド コンテキストのスタックです。