17

私は状態モナドを把握しようとしています。この目的のために、線形合同ジェネレーターを使用して一連の乱数を生成するモナド コードを書きたかったのです (おそらく良くありませんが、私の意図は状態モナドを学習することであり、良い RNG ライブラリを構築します)。

Boolジェネレーターはこれだけです (簡単にするために s のシーケンスを生成したい):

type Seed = Int

random :: Seed -> (Bool, Seed)
random seed = let (a, c, m) = (1664525, 1013904223, 2^32)  -- some params for the LCG
                  seed' = (a*seed + c) `mod` m
              in  (even seed', seed')   -- return True/False if seed' is even/odd 

数値について心配する必要はありません。これは、(Numerical Recipes によると) Ints の疑似ランダム シーケンスを生成するシードの単なる更新規則です。今、乱数を順番に生成したい場合は、次のようにします。

rand3Bools :: Seed -> ([Bool], Seed)
rand3Bools seed0  = let (b1, seed1) = random seed0
                        (b2, seed2) = random seed1
                        (b3, seed3) = random seed2
                    in  ([b1,b2,b3], seed3)

わかりましたので、State Monad を使用することで、このボイラープレートを回避できます。

import Control.Monad.State

data Random {seed :: Seed, value :: Bool}

nextVal = do 
   Random seed val <- get 
   let seed' = updateSeed seed
       val'  = even seed'
   put (Random seed' val')
   return val'

updateSeed seed = let (a,b,m) = (1664525, 1013904223, 2^32) in (a*seed + c) `mod` m

そして最後に:

getNRandSt n = replicateM n nextVal 

getNRand :: Int -> Seed -> [Bool]
getNRand   n seed = evalState (getNRandStates n) (Random seed True)

BoolOK、これは問題なく動作し、与えられたシードごとに n 個の疑似乱数のリストが表示されます。しかし...

私は自分がしたことを読んで(主にこの例に基づいています:http://www.haskell.org/pipermail/beginners/2008-September/000275.html)、それを複製して他のことをすることができます。しかし、do 記法とモナド関数 (replicateM など) の背後で実際に何が起こっているのか理解できないと思います。

誰かがこの疑問について私を助けることができますか?

1 - nextVal 関数の機能を理解するために desugar しようとしましたが、できませんでした。現在の状態を抽出して更新し、その状態を次の計算に渡すと推測できますが、これは、この do-sugar を英語であるかのように読み取ることに基づいているだけです。

この関数を元の >>= に実際に脱糖し、関数を段階的に返すにはどうすればよいですか?

put2 - 関数と関数が正確に何をするのか理解できませんでしたget。状態を「パック」および「アンパック」していると推測できます。しかし、ドーシュガーの背後にあるメカニズムは、まだ私にはとらえどころのないものです.

さて、このコードに関するその他の一般的な意見は大歓迎です。Haskell を使うと、動作するコードを作成して期待通りの動作をすることができると思うことがありますが、命令型プログラムで慣れているため、「評価に従う」ことはできません。

4

3 に答える 3

32

State モナドは、最初はややこしく見えます。Norman Ramsey が提案したように、最初から実装する方法を説明しましょう。警告、これはかなり長いです!

まず、State には 2 つの型パラメーターがあります。含まれている状態データの型と、計算の最終結果の型です。ここでは、それぞれの型変数としてstateDataとを使用します。result考えてみれば、これは理にかなっています。状態ベースの計算の特徴は、出力を生成しながら状態を変更することです。

次のように、型コンストラクターが関数を状態から変更された状態と結果に変換することはあまり明白ではありません。

newtype State stateData result = State (stateData -> (result, stateData))

したがって、モナドは「状態」と呼ばれますが、モナドによってラップされる実際の値は、含まれる状態の実際の値ではなく、状態ベースの計算の値です。

それを念頭に置いてrunState、 State モナドで計算を実行するために使用される関数が、実際にはラップされた関数自体のアクセサにすぎず、次のように定義できることに驚かないでください。

runState (State f) = f

では、State 値を返す関数を定義するとはどういう意味でしょうか? State がモナドであるという事実をしばらく無視して、基礎となる型だけを見てみましょう。まず、この関数を考えてみましょう (これは実際には状態に対して何もしません):

len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)

State の定義を見ると、ここではstateData型がIntであり、result型が であることがわかります。そのBoolため、データ コンストラクターによってラップされた関数の型は でなければなりませんInt -> (Bool, Int)。ここで、State-less バージョンのlen2State-- 明らかに、 type を持っていると想像してくださいString -> Bool。では、そのような関数を State ラッパーに適合する値を返す関数に変換するにはどうすればよいでしょうか?

明らかに、変換された関数はInt状態値を表す 2 番目のパラメーターを取る必要があります。また、別の状態値を返す必要がありますInt。この関数では状態に対して実際には何もしていないので、明らかな処理を行いましょう。その int をそのまま渡します。以下は、State-less バージョンに関して定義された State-shape 関数です。

len2 :: String -> Bool
len2 s = ((length s) == 2)

len2State :: String -> (Int -> (Bool, Int))
len2State s i = (len2' s, i)

しかし、それはちょっとばかげていて冗長です。変換を一般化して、結果の値を渡し、何でも State のような関数に変換できるようにしましょう。

convert :: Bool -> (Int -> (Bool, Int))
convert r d = (r, d)

len2 s = ((length s) == 2)

len2State :: String -> (Int -> (Bool, Int))
len2State s = convert (len2 s)

状態を変更する関数が必要な場合はどうすればよいでしょうか? convert状態を渡すように書いたので、明らかに で構築することはできません。シンプルにして、状態を新しい値で上書きする関数を書きましょう。どのようなタイプが必要ですか?新しい状態の値には が必要でInt、もちろん関数を返す必要がありstateData -> (result, stateData)ます。それが State ラッパーに必要だからです。result状態値を上書きしても、実際には State 計算の外部に意味のある値はありません。したがって、ここでの結果()は、Haskell で「値なし」を表すゼロ要素のタプルになります。

overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)

それは簡単でした!では、実際にその状態データを使って何かをしてみましょう。len2State上記をより賢明なものに書き直してみましょう: 文字列の長さと現在の状態値を比較します。

lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)

前に行ったように、これをコンバーターとステートレス関数に一般化できますか? それほど簡単ではありません。len関数は状態を引数として取る必要がありますが、状態を「認識」させたくありません。ぎこちない、確かに。ただし、すべてを処理する簡単なヘルパー関数を作成できます。状態値を使用する必要がある関数を指定すると、値が渡され、状態型の関数にすべてがパッケージ化されます。len賢明なことは何も残していません。

useState :: (Int -> Bool) -> Int -> (Bool, Int)
useState f d = (f d, d)

len :: String -> Int -> Bool
len s i = (length s) == i

lenState :: String -> (Int -> (Bool, Int))
lenState s = useState (len s)

ここで、注意が必要なのは、これらの関数をつなぎ合わせたい場合はどうするかということです。文字列で使用したいとしましょうlenState。結果が false の場合は状態値を 2 倍にし、文字列をもう一度チェックして、いずれかのチェックが成功した場合は最後に true を返します。このタスクに必要なパーツはすべて揃っていますが、すべてを書き出すのは大変です。それぞれが状態のような関数を返す 2 つの関数を自動的に連鎖させる関数を作成できますか? 確実なこと!result最初の関数によって返される State 関数と、前の関数の型を引数として取る関数の 2 つを引数として取るようにする必要があるだけです。それがどのようになるか見てみましょう:

chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
                       in f r d'

これが行っているのは、最初の状態関数を何らかの状態データに適用し、次に 2 番目の関数を結果と変更された状態データに適用することだけです。シンプルですね。

さて、興味深い部分: と の間chainStatesconvert、ステートレス関数の任意の組み合わせをステート対応関数にほぼ変換できるはずです! 今必要なのはuseState、結果として状態データを返す の置き換えだけです。これにより、chainStates は、プルしているトリックについて何も知らない関数にそれを渡すことができます。また、ラムダを使用して前の関数からの結果を受け取り、それらに一時的な名前を付けます。さて、これを実現しましょう:

extractState :: Int -> (Int, Int)
extractState d = (d, d)

chained :: String -> (Int -> (Bool, Int))
chained str = chainStates  extractState         $ \state1 ->
              let check1 = (len str state1) in
              chainStates (overwriteState (
                  if check1 
                  then state1 
                  else state1 * 2))             $ \ _ ->
              chainStates  extractState         $ \state2 ->
              let check2 = (len str state2) in
              convert (check1 || check2)

そして試してみてください:

> chained "abcd" 2
(True, 4)
> chained "abcd" 3
(False, 6)
> chained "abcd" 4
(True, 4)
> chained "abcdef" 5
(False, 10)

もちろん、State は実際には State のような関数をラップしてそれらから遠ざけるモナドであることを忘れてはなりません。それとも彼らはしますか?衝撃的なひねりを加えて、実際の State モナドはすべて同じ関数を異なる名前で提供することが判明しました。

runState (State s) = s
return r = State (convert r)
(>>=) s f = State (\d -> let (r, d') = (runState s) d in
                         runState (f r) d')
get = State extractState
put d = State (overwriteState d)

>>= は chainStates とほぼ同じですが、chainStates を使用して定義する良い方法がありませんでした。まとめとして、実際のStateを使用して最後の例を書き直すことができます。

chained str = get                               >>= \state1 ->
              let check1 = (len str state1) in
              put (if check1 
                  then state1 else state1 * 2)  >>= \ _ ->
              get                               >>= \state2 ->
              let check2 = (len str state2) in
              return (check1 || check2)

または、同等の do 表記ですべて詰め込みます。

chained str = do
        state1 <- get
        let check1 = len str state1
        _ <- put (if check1 then state1 else state1 * 2)
        state2 <- get
        let check2 = (len str state2)
        return (check1 || check2)
于 2009-12-24T08:28:21.190 に答える
8

まずval、州のモナドにを格納する必要がないため、例は非常に複雑です。シードのみが永続状態です。次に、標準の状態モナドを使用する代わりに、すべての状態モナドとその操作をそれらのタイプで自分で再実装すると、運が良くなると思います。この方法でもっと学ぶことができると思います。ここにあなたが始めるためのいくつかの宣言があります:

data MyState s a = MyState (s -> (s, b))

get :: Mystate s s
put :: s -> Mystate s ()

次に、独自の接続詞を作成できます。

unit :: a -> Mystate s a
bind :: Mystate s a -> (a -> Mystate s b) -> Mystate s b

ついに

data Seed = Seed Int
nextVal :: Mystate Seed Bool

脱糖の問題に関しては、do使用している表記法はかなり洗練されています。しかし、脱糖は一度に1行ずつの機械的手順です。私が理解できる限り、あなたのコードは次のように脱糖する必要があります(私が同意しない元のタイプとコードに戻ります):

 nextVal = get >>= \ Random seed val ->
                      let seed' = updateSeed seed
                          val'  = even seed'
                      in  put (Random seed' val') >>= \ _ -> return val'

入れ子の構造をもう少し明確にするために、インデントで大きな自由を取りました。

于 2009-12-24T03:46:08.330 に答える
5

素晴らしい回答がいくつかあります。State モナドを操作するときに私が行うことは、私の頭の中で置き換えることですState s a(s -> (s,a)結局のところ、それは実際にそうです)。

次に、次のような bind の型を取得します。

(>>=) :: (s -> (s,a)) ->
         (a -> s -> (s,b)) ->
         (s -> (s,b))

bind は特殊な種類の関数合成演算子であることがわかります。(.)

状態モナドに関するブログ/チュートリアルをここに書きました。これはおそらく特に優れているわけではありませんが、これを書くことで物事を少し理解するのに役立ちました。

于 2009-12-25T19:43:15.520 に答える