10

私はついにモナドの使い方を理解しました(私がそれらを理解しているかどうかはわかりません...)が、私のコードは決して非常にエレガントではありません。Control.Monadこれらすべての機能が実際にどのように役立つかについての理解が不足しているためだと思います。したがって、状態モナドを使用して、特定のコードでこれに関するヒントを求めるのがよいと思いました。

このコードの目的は、さまざまな種類のランダムウォークを計算することです。これは、より複雑なものの前に私がやろうとしていることです。問題は、同時に2つのステートフル計算があり、それらを優雅に構成する方法を知りたいということです。

  1. 乱数発生器を更新する関数は、ある種のものです。Seed -> (DeltaPosition, Seed)
  2. ランダムウォーカーの位置を更新する関数は、ある種のタイプですDeltaPosition -> Position -> (Log, Position)Logランダムウォーカーの現在の位置を報告するための何らかの方法があります)。

私がやったことはこれです:

この2つのステートフル計算を構成する関数があります。

composing :: (g -> (b, g)) -> (b -> s -> (v,s)) -> (s,g) -> (v, (s, g))
composing generate update (st1, gen1) = let (rnd, gen2) = generate gen1
                                            (val, st2)  = update rnd st1
                                        in (val, (st2, gen2))

次に、それを状態を構成する関数に変換します。

stateComposed :: State g b -> (b -> State s v) -> State (s,g) v
stateComposed rndmizer updater = let generate = runState rndmizer
                                     update x = runState $ updater x
                                 in  State $ composing generate update 

そして、私は最も単純なものを持っています。たとえば、現在の位置に乱数を合計するだけのランダムウォーカーです。

update :: Double -> State Double Double
update x = State (\y -> let z = x+y
                        in  (z,z))

generate :: State StdGen Double
generate = State random

rolling1 = stateComposed generate update 

そしてこれを繰り返し行う関数:

rollingN 1 = liftM (:[]) rolling1
rollingN n = liftM2 (:) rolling1 rollings
    where rollings = rollingN (n-1) 

そして、これをロードしghciて実行すると、次のようになります。

*Main> evalState (rollingN 5) (0,mkStdGen 0)
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]

ランダムウォーカーが占める位置のリストである、私が欲しいものを手に入れます。しかし...これを行うにはもっとエレガントな方法があるに違いないと思います。2つの質問があります:

  1. の巧妙な関数を使用して、これらの関数をより「モナディック」な方法で書き直すことはできますControl.Monadか?

  2. このような状態の組み合わせで使用できる一般的なパターンはありますか?これはモナド変換子などと関係がありますか?

4

2 に答える 2

11

State更新:実際には、モナドをまったく必要としない、これを行うためのはるかに優れた方法があることに言及する必要がありました。

takeStep :: (Double, StdGen) -> (Double, StdGen)
takeStep (p, g) = let (d, g') = random g in (p + d, g')

takeSteps n = take n . tail . map fst $ iterate takeStep (0, mkStdGen 0)

必要に応じて機能します。

*Main> takeSteps 5
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]

2 つの別々のステートフルな計算を「構成」するという考えにコミットしていない場合は、同じことをより簡単に実現できます。

takeStep :: State (Double, StdGen) Double
takeStep = do
  (pos, gen) <- get
  let (delta, gen') = random gen
  let pos' = pos + delta
  put (pos', gen')
  return pos'

takeSteps n = evalState (replicateM n takeStep) (0, mkStdGen 0)

これにより、例と同じ出力が生成されます。

*Main> takeSteps 5
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]

State Aこのアプローチ ( andを構成しようとするのではなく、単一のモナドですべての状態操作を行うState B) は、最もエレガントなソリューションのように思えます。


更新: モナドトランスフォーマーを使用してモナドをスタックすることについての質問に答えるにはState: 確かに可能です。たとえば、次のように記述できます。

update' :: (Monad m) => Double -> StateT Double m Double
update' x = StateT $ \y -> let z = x + y in return (z, z)

generate' :: (Monad m) => StateT StdGen m Double
generate' = StateT $ return . random

takeStep' :: StateT Double (State StdGen) Double
takeStep' = update' =<< lift generate'

takeSteps' n = evalState (evalStateT (replicateM n takeStep') 0) $ mkStdGen 0

逆の順序で積み重ねることもできます。

このバージョンでも同じ出力が生成されますが、私の意見では、非StateTバージョンの方が少し明確です。

于 2010-07-30T23:11:09.073 に答える