18

"Monads for the Curious Programmer" を読んで、Haskell モナドを理解しようとしています。リストモナドの例に出くわしました:

tossDie=[1,2,3,4,5,6]

toss2Dice = do
    n <- tossDie
    m <- tossDie 
    return (n+m)

main = print toss2Dice

doブロックがm36 要素のリストとして生成される方法は、ある程度理解できます。すべての要素nを 6 つの要素のリストとしてマップし、それらのリストを連結します。私が理解していないnのは、 の存在によって がどのように変更され、m <- tossDie6 つの要素リストから 36 要素に変わるかということです。明らかに「最初にバインドnしてからバインドするm」というのは間違った理解ですが、何が正しいのでしょうか。

doまた、2 引数関数がブロックでどのように適用されるかについても完全には明確ではありません。キュアリングのケースだと思いますが、正確にどのように機能するかについては少しぼんやりしています。

上記の2つの謎を誰か説明してもらえますか?

4

4 に答える 4

13

私が推測する興味深いビットはこれです:

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は、リストモナドの表記法で非常に簡単になり、読みやすくなります。(実際には、可能なすべての組み合わせを生成しているだけなのに、単一の値で作業しているように見えます!)

于 2013-11-13T18:57:04.860 に答える