6

私は Haskell を少しずつ学んでおり、State モナドの理解に (ゆっくりと) 取り組んでいます。状態がブール値のテストを満たすまで State 計算を繰り返す関数を書き、全体的な結果のリストに返された値を収集しようとしています。 . 私は最終的にこれで成功しました:

collectUntil :: (s -> Bool) -> State s a -> State s [a]
collectUntil f s = do s0 <- get
                      let (a,s') = runState s s0
                      put s'
                      if (f s') then return [a] else liftM (a:) $ collectUntil f s

となることによって

simpleState = state (\x -> (x,x+1))

*Main> evalState (collectUntil (>10) simpleState) 0
[0,1,2,3,4,5,6,7,8,9,10]

これはこのタスクにとって合理的な機能ですか、それとももっと慣用的な方法がありますか?

4

3 に答える 3

10

liftMあなたは、私が最初にモナディックコードを書き始めたときに犯したのとまったく>>=同じ間違いを犯してい<-ます。

理想的には、州のモナドについて言及しrunStateたりevalState、州のモナドの内部にいる必要はまったくありません。必要な機能は次のとおりです。

  • 現在の状態を読む
  • 述語fを満たす場合は、
  • そうでない場合は、計算sを実行し、その結果を出力に追加します

これは、次のように直接行うことができます。

collectUntil f comp = do
    s <- get                              -- Get the current state
    if f s then return []                 -- If it satisfies predicate, return
           else do                        -- Otherwise...
               x  <- comp                 -- Perform the computation s
               xs <- collectUntil f comp  -- Perform the rest of the computation
               return (x:xs)              -- Collect the results and return them

同じモナドの一部である場合は、doステートメントをネストできることに注意してください。これは非常に便利です。ifステートメントの両方の分岐が同じモナディックタイプにつながる限り、1つのdoブロック内で分岐できます。

この関数の推定タイプは次のとおりです。

collectUntil :: MonadState t m => (t -> Bool) -> m a -> m [a]

必要に応じて、タイプに特化することができますが、次のことState sを行う必要はありません。

collectUntil :: (s -> Bool) -> State s a -> State s [a]

後で別のモナドを使用したい場合に備えて、より一般的な状態を維持することも望ましい場合があります。

直感は何ですか?

sステートフル計算であり、ステートモナド内にいるときはいつでも、次のことができます。

x <- s

これでx、計算の結果が得られます(evalState初期状態で呼び出してフィードしたかのように)。状態を確認する必要がある場合は、次のことができます。

s' <- get

s'現在の状態の値になります。

于 2012-06-28T19:01:34.040 に答える
4

ほとんどのモナドには、 、 などのいくつかのプリミティブな「実行」操作が付属していrunStateますexecStaterunState 状態モナド内で頻繁に呼び出している場合は、モナドが提供する機能を実際には使用していないことを意味します。あなたが書いた

s0 <- get                    -- Read state
let (a,s') = runState s s0   -- Pass state to 's', get new state
put s'                       -- Save new state

状態を明示的に渡す必要はありません。これが状態モナドの機能です。あなたはただ書くことができます

a <- s

それ以外の場合、関数は妥当に見えます。「if」の両方のブランチの結果の一部であるためa、明確にするためにそれを除外することをお勧めします。

collectUntil f s = step
  where
    step = do a <- s
              liftM (a:) continue
    continue = do s' <- get
                  if f s' then return [] else step
于 2012-06-28T18:41:37.607 に答える
2

このような単純なタスクには、Stateモナドを使用しません。他の人は、モナドバージョンを実際にどのように書くべきかをすでに明確にしていますが、それを書くための最も慣用的な方法を求めているので、私の個人的な (より単純な) 解決策を追加したいと思います。

collectWhile, collectUntil :: (a -> a) -> (a -> Bool) -> a -> [a]
collectWhile f cond z = takeWhile cond $ iterate f z
collectUntil f cond z = collectWhile f (not . cond) z

または、必要な場合は次の行だけで十分ですcollectUntil

collectUntil f cond z = takeWhile (not.cond) $ iterate f z

ここでtakeWhileiteratePreludeからのものです。完全を期すために、これは実装のコアであるため、反復の (非常に単純な) コードは次のとおりです。

iterate f x =  x : iterate f (f x)

警告: おそらくこれは私の回答からは十分に明確ではありませんでしたが、この解決策は実際には同じではありませんState。もちろん、中間状態または結果のリストをそれぞれ使用f :: (s, a) -> (s, a)して射影map fstまたは取得することにより、非常に似たようなことを行うことができます。map sndただし、この時点で表記を簡単にするために、 でソリューションを使用する方が簡単かもしれませStateん。

于 2012-06-28T20:17:12.250 に答える