3

/どのように/バインド演算子が実際に次の State モナドをバインドするかを理解するのに苦労しています:

pop :: State [Int] Int
pop = do
        (x:xs) <- get
        put xs
        return x

push :: Int -> State [Int] ()
push x = do 
            xs <- get
            put (x:xs)

doStuff :: State [Int] ()
doStuff = do
            pop
            x <- pop
            push 5
            push x

Take doStuff、次のように脱糖できます。

pop >>= (\_ -> pop >>= (\x -> push 5 >>= (\_ -> push x)))

この行が評価されると、バインディングは実際にどのような順序で行われるのでしょうか? 実際にバインドするには、Haskell は演算子の右側の関数から State モナドを取得する必要があるため>>=(つまり、関数の右側のオペランドを最初に完全に評価する必要があります)、次のようになると考えていました。

  1. s1 =push 5 >>= (\_ -> push x)
  2. s2 =pop >>= (\x -> s1)
  3. s3 =pop >>= (\_ -> s2)

これはそれについて考える正しい方法ですか?モナドについてはよく理解できたと思いますが、最大の問題は、いわば「舞台裏」で何が起こっているか、データがどのように流れているかを実際に視覚化することです。このdo表記法は、一連の一連の操作を扱っているような錯覚を与えますが、実際には、ネストとクロージャーがたくさんあります。

ここで物事を考えすぎて、結果として自分自身をさらに混乱させているような気がします。

4

3 に答える 3

8

から始まる

pop >>= (\_ -> pop >>= (\x -> push 5 >>= (\_ -> push x)))

いくつかの関数はインライン化できます (何が起こっているかをよりよく示すため)。簡単にするために、 がトランスフォーマーまたは newtype として定義されていない(>>=)ふりをして、 から始めます。State

type State s a = s -> (a, s)
m >>= k = \ s -> let (a, s') = m s in k a s'

\ s -> let (a, s') = pop s in
(\ _ -> pop >>= (\ x -> push 5 >>= (\ _ -> push x))) a s'

\ s -> let (_, s') = pop s in
(pop >>= (\ x -> push 5 >>= (\ _ -> push x))) s'

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
(\ x -> push 5 >>= (\ _ -> push x)) a s''

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
(push 5 >>= (\ _ -> push a)) s''

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
let (b, s''') = push 5 s'' in
(\ _ -> push a)) b s'''


\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
let (_, s''') = push 5 s'' in
push a s'''
于 2013-04-10T13:20:12.587 に答える
3

これはそれについて考える正しい方法ですか?

いいえ。

まず第一に、モナドで「最初にこれが起こり、それよりもこのキーボード入力を評価する...」と考えるのは明らかに正しいですがIO、これはすべてのモナドに当てはまるわけではありません。たとえば、リストモナドでは、これはまったく意味がありません。一般に、Haskell の計算に特定の順序を割り当てることはまったく不可能であり、それは定義された動作ではありません。

それでも、モナド内の計算の順序を考えることは常に可能doであり、非常に多くの場合に役立ちます。実際、この順序は表記法によって示唆されているものです。そのため、ほとんどの場合、脱糖表現について考えるのは実際には洞察力に富むものではありません。しかし、そのステップを実行したい場合は、次のようにします。

  1. pop >>= \_ -> THUNK1

  2. サンク1 ≡>pop >>= \x -> THUNK2

  3. { Closure{x}} サンク2 ≡>push 5 >>= \_ -> THUNK3

  4. { Closure{x}} サンク3 ≡>push x

もちろん、これはグロテスクなほど醜いですが、砂糖漬けのdo表現とほとんど同じです.

于 2013-04-10T13:09:22.720 に答える
2

この行が評価されると、バインディングは実際にどのような順序で行われるのでしょうか?

ここでの「バインディング」について特別なことは何もありません。脱糖された式は、他の式とまったく同じ (遅延) 方法で評価されます。詳細は(>>=)、使用している特定のモナドの実装に依存します。

のような式を使用することについて話している場合runState、 のような式が与えられた場合foo >>= (\x -> bar)、最も外側の式は の適用ですが、 a のラップを解除して内部の関数を適用(>>=)しようとしているため、 関数と同様に が強制されます。newtype(>>=)

代わりにリストモナドを考えると、(>>=)isconcatMapです。のような式が与えられた場合、結果[foo1, foo2] >>= (\x -> [bar1, bar2, x] >>= (\y -> [baz, y]))に対して を使用take 5しても、すべてのバインドが完全に計算されないことは明らかです。


とは言っても、ここには重要なルールが 1 つあります。どの程度評価x >>= fが の評価を強制する場合でもx、脱糖ブロックのような大きな式ではdo、強制は見かけの「順次」順序で発生します。これは、「順次錯覚」が可能。

于 2013-04-10T13:09:44.417 に答える