ハスケル:状態を反復する、私が望む振る舞いを強制する方法?

これは私の最初の投稿であり、私は比較的新しいHaskellですので、間違いを犯したり、私のコードが慣用的でない場合はどうぞ!

a、f(a)、f(f(a))...の2つの直感的な説明を考えてみましょう。

A. a list containing: a, the application of f to a, the application of f to that, the application of f to that...

B. a list containing, in the ith position, i nested applications of f to a.

私の問題は、Haskellで A を行うために iterate 関数を使用しようと焦げたことです。私の実際のアプリケーションはシミュレーションですが、以下のような工夫した例ではこの問題を強調しています。

import Control.Monad.State

example :: State Int [[String]]

step :: [String] -> State Int [String]
step l = do
         currentState <- get
         let result = if (currentState == 1)
                          then "foo":l
                          else "bar":l
         put (currentState + 1)
         return result

example = do
          sequence $ take 3 . iterate (>>= step) $ return []

これらの定義により、

evalState example 1

結果:

[[],["foo"],["bar","bar"]]

明らかに、 iterate A ではなく B です。 step 関数は入力リストに何か追加するので、 step ["foo"] bar "、" bar "] 、状態に関係なく!

私は、ここで何が起こっているのか、そして正式には結果がまさに「そうあるべきもの」であることを理解しているとしましょう。 step はステートフルな関数であり、したがって、f(a)がf(f(a))の一部として評価のために出現すると、状態が変化したために2番目のリスト項目から取り出されるのではなく、再計算されます。私はまた、私の実際の生活の中で私の蓄積リストを州の中に入れて、これを避けることができることを理解しています。

それにもかかわらず、これを掲示する理由は2つある。

まず、 iterate は初心者が実際に B A していると誤解する可能性がある方法でよく説明されています>。これには、 Learn You A Haskell (それ以外は信じられないほど役に立つと思う)が含まれますが、SO(ここここ)。実際、LYAHFGGの iterate の口頭での説明は、ほぼ正確には A の定義です。ですから、このためにバグを取得し、説明を探している他のHaskell初心者のためのリソースとして、これを投稿することは有益かもしれません(もっと正確で、技術的で、より良いフレーズを投稿します。以下の A B の違い)。

第二に、実際には A する機能があるかどうかにも関心があります。言い換えれば、上記のステートフルな例では、(a、b = f(a)、f(b)、...)のようにリストを生成するにはどうすればよいでしょうか?換言すれば、与えられた

example2 = do
           firstResult <- step []
           secondResult <- step firstResult
           return $ [[], firstResult, secondResult]

そのために

evalState example2 1

所望の結果をもたらす

[[],["foo"],["bar","foo"]]

iterate を使用して example2 を書き直すにはどうすればよいですか?

初心者のHaskellのリストには、関連する質問がありますmemoizing版の iterate が投稿されました。ただし、そのクエリは回答を受け取っていないようです。

私は怠惰が本当に私のアプリケーションで問題であるかどうかは完全にはわかりません。厳密なバージョンの iterate は私がしたいことをしますか?以下のような私自身の素朴な「厳しい反復」は何の違いも生じていないようです。

iterate' f x = x : rest
               where previous = f x
                     rest = previous `seq` iterate f previous

これに関するすべての洞察は非常に高く評価されます。

4

2 答え

There is no difference between A. and B., they are the same thing by referential transparency.
The core of the problem seems to be that you're interpreting them in the context of execution of stateful computations. In that context, the analogue of A that you're expecting is
A': Produce a result list by 1. putting the result of the initial computation into the list, 2. determine the next computation from the previous result, execute it and append its result to the list, 3. goto 2.
The analogue of B is
B': Produce a list of computations (by iterating (>>= step)) and from that a result list by executing the computations one after the other.
For stateless computations, or when you pass the same initial state to all computations produced in B', the only difference is in efficiency, but if you're using sequence, each computation starts with a different state, so you get different results from A'. Breaking down your example, we have

actionList = take 3 $ iterate (>>= step) (return [])
           = [return [], return [] >>= step, return [] >>= step >>= step]

State Int [String] のアクション(またはモナド値)のリスト。さて、 sequence を適用すると、

example = sequence actionList

実行されると、最初の状態で最初のアクションが実行され、2番目の状態は最初の状態で更新され、3番目の状態は2番目の状態によって更新された状態で実行されるアクションを取得します。あなたが期待する動作を得るためには、すべて同じ初期状態で実行する必要があります。

Basically, a value of type State s v is a function of type s -> (v, s). iterate creates a list of functions, and sequence applys these functions, supplying different s arguments to them (each gets the s produced by the previous).

望ましい振る舞いを得るためには、新しいコンビネータを導入する必要があります。非常にいいですが、ごく少数のモナドでしか使えません。

iterateM :: Monad m => (a -> m a) -> m a -> m [a]
iterateM step start = do
    first <- start
    rest <- iterateM step (step first)
    return (first:rest)

それは生産する

Prelude Control.Monad Control.Monad.State> evalState (fmap (take 4) (iterateM step (return []))) 1
[[],["foo"],["bar","foo"],["bar","bar","foo"]]

But it works only in monads with sufficiently lazy (>>=), Control.Monad.State.Lazy is one of the few, Control.Monad.State.Strict is not. And even with C.M.S.Lazy, You cannot use the state after an iterateM, you have to put a new state before you can continue the computation. To get something usable with other monads, we could add a count parameter,

iterCountM :: (Monad m) => Int -> (a -> m a) -> m a -> m [a]
iterCountM 0 _ _ = return []
iterCountM k step start = do
    first <- start
    rest <- iterCountM (k-1) step (step fisrt)
    return (first:rest)

柔軟性は失われますが、より多くのモナドでユーザビリティが向上します。

16
追加された
応答に多くの感謝! 「あなたの例を壊す」の後で言うことはすべて完璧な意味を持ち、「私がここで何が起こっているのか理解していると言わせてください...」という意味です。しかし、私はまだその解釈に納得していない。 step ["foo"] と等しい step を最初の要素の完全に評価された結果に適用することを A ["bar"、 "bar"] は決してありません。州に関係なく。この解釈を念頭に置いて、この例では B を実装していますが、 A では実装していません。
追加された 著者 Bilal Barakat,
同一の初期状態でシーケンスされたすべてのアクションを実行することは意味があります。しかし、実際のアプリケーションでは、状態にはランダムジェネレータも含まれており、各ステップにいくつかの確率的イベントが発生します。したがって、たとえ同じ乱数ジェネレータが結果リストの各ポジションの「最初の」ステップに供給されたとしても、異なる結果をもたらす可能性があります。しかし、2番目のエントリは、もはや最初のエントリの進化として解釈することはできません...
追加された 著者 Bilal Barakat,
はい、それはまさに私が考えているパターンです。つまり、 example2 (元の質問に編集)を参照すると、「あなたは iterate を使って書き直すことはできません! ?それでは、コンパクトでエレガントな/慣用的なやり方は何でしょうか?私が言ったように、私は状態の中に結果を累積することなくこれを行う方法に興味があるでしょう。
追加された 著者 Bilal Barakat,
ダニエル、別の答えに2番目のコメント(文脈で)を書いてもらうと、私はそれを投票することができます。(最初の答えは確かに example それが何をしているのか、私は代わりに何か他のことをする方法についてのガイダンスを探していただけです!)
追加された 著者 Bilal Barakat,
それは信じられないほど助けになります、あなたの寛大な助けをありがとう!
追加された 著者 Bilal Barakat,
私の唯一の疑問は、答えが iterate B )、 iterateM (本当に A を実行する)は本当に異なっています... AとBを表現する方法があれば、 2つのイテレーターが異なるのと同じように)、それに応じて質問と回答の両方を編集できます。私はそれが私の立場にある人にとってより有用な参考資料になると思う。私はそれを受け入れた後、答えを編集することはできないと思いますか?
追加された 著者 Bilal Barakat,
@Carl参加してくれてありがとうございますが、ダニエルはすでに私のためにそれをすべてクリアしました。私が見逃していたパズルの部分は、あなたが思うものではありませんが、決して気にしません。申し訳ありませんが、SO初心者として:私はそれらの行に沿ってさらにコメントを募ることを避けるために元の質問を編集するはずですか?
追加された 著者 Bilal Barakat,
あと、新しいタイプのラッパーを無視して、繰り返しのnewtypeとcurrying、(>> = step)::(([String]、Int) - >([String]、Int) ) - >(([String]、Int) - >([String]、Int)))。各関数は、この変換を前の関数に適用した結果です(完全に評価されているかどうかは、参照透過性のために重要ではありません)。前の関数のアプリケーションの結果を次の関数の判定に含めるには、それは反復ではなく別のパターンです。
追加された 著者 Daniel Fischer,
@ビラル、私は元の答えにガイダンスを追加し、それが助けてくれることを願っています。
追加された 著者 Daniel Fischer,
受け入れられた回答を編集することは可能だと思いますが、それを明確にすることを考えさせてください。要点は、iterateの型ではAとBは同じものですが、A 'を別の型にしたいということです。
追加された 著者 Daniel Fischer,
@BilalBarakat AとBは同じものです。最初にstepを呼び出すときの最初の状態は常に1であると仮定しています。シーケンスを使用しているため、そうではありません。状態のコンテキストでは、シーケンスとは、「現在の状態で最初のアクションを実行し、結果の状態を次のアクションに渡して実行し、最後のアクションの結果になるように現在の状態を更新する」という意味です。あなたはそれが「現在の状態で各行動を実行する」と仮定しているようです。
追加された 著者 Carl,

これは、あなたが提出した質問には答えられないかもしれませんが、あなたがしていることは unfoldr のように非常によく聞こえます。

step (seed, l) = Just (l', (seed', l'))
  where seed' = succ seed
        l' = (if seed == 1 then "foo" else "bar"):l

ghci> take 3 $ unfoldr step (1, [])
[["foo"], ["bar", "foo"], ["bar", "bar", "foo"]]

モナドは必要ありません。あなたが実際に何をしようとしているのかを指定していないので、私は暗闇の中で刺すようですが、 step を正しいかどうかにかかわらず、単純な状態のスレッディングにも使用できます。

unfoldr :: (seed -> Maybe (val, seed)) -> seed -> [val]
3
追加された
ありがとう@ダン、素敵なtipp、と私はunfoldr(私はmapAccumLを使用して調べていたが)認識していませんでした。それを私のツールキットに追加するのは間違いありませんが、私の現在の目的では、それぞれの "ステップ"はかなり複雑であり、まだ拡張されるかもしれないので、モナールの一般性はおそらく有益でしょう。
追加された 著者 Bilal Barakat,
過小評価されたunfoldrを促進するための+1。
追加された 著者 Daniel Fischer,