私が推測する興味深いビットはこれです:
toss2Dice = do
n <- tossDie
m <- tossDie
return (n+m)
これは、次の Python といくらか同等です。
def toss2dice():
for n in tossDie:
for m in tossDie:
yield (n+m)
リスト モナドに関して言えば、<-
do 表記のバインド矢印 ( ) を従来の命令型の "foreach" ループと見なすことができます。その後のすべて
n <- tossDie
tossDie
はその foreach ループの「ループ本体」に属しているため、 に割り当てられた値ごとに 1 回評価されn
ます。
do
記法から実際のバインド演算子への脱糖が必要な場合は>>=
、次のようになります。
toss2Dice =
tossDie >>= (\n ->
tossDie >>= (\m ->
return (n+m)
)
)
「内側のループ本体」
(\n ->
tossDie >>= (\m ->
return (n+m)
)
)
の値ごとに 1 回実行されますtossDie
。これは、ネストされた Python ループとほとんど同じです。
技術的な大雑把: バインディングの矢印から "foreach" ループが発生する理由は、使用している特定のモナドに関係しています。矢印はモナドごとに異なる意味を持ち、特定のモナドでそれらが何を意味するかを知るには、いくつかの調査を行い、そのモナドが一般的にどのように機能するかを理解する必要があります。
アローは、異なるモナドに対して異なる動作をする bind 演算子への呼び出しに desugar され>>=
ます – これが、<-
異なるモナドに対してバインドアローが異なる動作をする理由です!
リストモナドの場合、bind 演算子>>=
はリストを左側に、リストを返す関数を右側に取り、その関数をリストのすべての要素に適用します。面倒な方法でリスト内のすべての要素を 2 倍にしたい場合は、次のようにすると想像できます。
λ> [1, 2, 3, 4] >>= \n -> return (n*2)
[2,4,6,8]
(return
は型を機能させるために必要です。>>=
はリストを返す関数を期待しておりreturn
、リスト モナドの場合は値をリストにラップします。) おそらくより強力な例を説明するために、次の関数を想像することから始めることができます。
λ> let posneg n = [n, -n]
λ> posneg 5
[5,-5]
それから私たちは書くことができます
λ> [1, 2, 3, 4] >>= posneg
[1,-1,2,-2,3,-3,4,-4]
-4 から 4 までの自然数を数えます。
リスト モナドがこのように機能する理由は、bind 演算子のこの特定の動作が>>=
モナドreturn
の法則を保持するためです。モナドの法則は私たち (そしておそらく冒険好きなコンパイラー) にとって重要です。
これの非常にかわいい副作用は、リストが値の不確実性を表すのに非常に便利になることです: たとえば、画像を見てテキストに変換する OCR を構築しているとします。4 または A または H のいずれかである可能性のある文字に遭遇する可能性がありますが、確実ではありません。リストモナドで OCR を機能させ、リストを返すこと['A', '4', 'H']
で、ベースをカバーしました。スキャンされたテキストを実際に操作することdo
は、リストモナドの表記法で非常に簡単になり、読みやすくなります。(実際には、可能なすべての組み合わせを生成しているだけなのに、単一の値で作業しているように見えます!)