問題は、Control.Monad.State.Lazy(>> =)が非常に怠惰であるため、($!)でさえ役に立たないことです。
Control.Monad.State.Strictを試してください。これは、($!)に到達するはずです。
怠惰な状態のモナドの(>> =)は、(値、状態)のペアをまったく調べないため、最後に到達する前に評価を行う唯一の方法は、ペアf
をm >>= f
分解することです。これはここでは発生しないため、runStateが最終的に結果を求めている場合、スタックには大きすぎる巨大なサンクが発生します。
さて、私は食べました、今私は詳しく説明することができます。怠惰なモナドの古い(mtl-1.x)定義を使用State s
させてください。内側のモナドがないと、そこが少し見やすくなります。新しい(mtl-2.x)定義type State s = StateT s Identity
は同じように動作し、書き込みと読み取りが増えます。(>> =)の定義は
m >>= k = State $ \s -> let
(a, s') = runState m s
in runState (k a) s'
さて、let
バインディングは怠惰なので、これは
m >>= k = State $ \s -> let
blob = runState m s
in runState (k $ fst blob) (snd blob)
より読みやすいだけです。したがって、(>> =)を使用すると、ブロブは完全に未評価になります。評価は、続行する方法を決定するk
ために検査する必要がある場合、または検査する必要がある場合にのみ必要です。fst blob
k a
snd blob
ではreplicateM r tick
、計算は(>>)で連鎖されているため、k
in(>> =)の定義はconst tick
です。定数関数として、それは間違いなくその引数を検査する必要はありません。だからtick >> tick
_
State $ \s ->
let blob1 = (\n -> let n' = n+1 in seq n' ((),n')) s
blob2 = (\m -> let m' = m+1 in seq m' ((),m')) (snd blob1)
in blob2
評価が必要になるseq
まで触れられませんblobN
。しかし、それを最も外側のコンストラクター(ペアコンストラクター)で評価する必要があると(,)
、seqをトリガーするのに十分であり、それがここでの完全な評価につながります。さて、では、に達した後のmillion
決勝まで、何も評価を必要としません。その時までに、百万層のサンクが作られました。そのサンクを評価するには、初期状態に達するまでスタックに多くをプッシュする必要があり、スタックがそれらを保持するのに十分な大きさである場合、それらはポップされて適用されます。つまり、3つのトラバーサル、1。サンクの構築、2。サンクからのレイヤーの剥離とスタックへのプッシュ、3。スタックの消費になります。snd
runState
let m' = m+1 in seq m' ((),m')
Control.Monad.State.Strictの(>> =)は、各バインドでsを強制するのに十分厳密であるためseq
、トラバーサルは1つだけで、(重要な)サンクは作成されず、計算は一定のスペースで実行されます。定義は
m >>= k = State $ \s ->
case runState m s of
(a, s') -> runState (k a) s'
重要な違いは、式のパターンマッチングcase
が厳密であるということです。ここでは、blob
のパターンと照合するために、最も外側のコンストラクターに対して評価する必要がありcase
ます。本質的な部分
でm = tick = State (\m -> let m' = m+1 in seq m' ((),m'))
case let s' = s+1 in seq s' ((),s') of
(a, s'') -> runState (k a) s''
パターンマッチでは、((), s')
[(、)コンストラクターへの]の評価が必要です。seq
これは、の評価に関連付けられているためs' = s+1
、すべてがバインドごとに完全に評価され、サンクやスタックはありません。
ただし、それでも注意する必要があります。この場合、seq
(($!)
または)が含まれるタイプの構造が浅いため、評価は。の適用に追いつきました(>>)
。一般に、より深い構造化タイプを使用する場合、および/またはseq
sを使用しない場合、CMSStrictはスタックオーバーフローにつながる可能性のある大きなサンクも作成します。サンクは、そのような状況でCMSLazyによって生成されたものよりも少し単純で、絡み合いが少なくなっています。
一方、CMSLazyの怠惰により、CMSStrictでは不可能な他の計算が可能になります。たとえば、CMSLazyは、数少ないモナドの1つを提供します。
take 100 <$> mapM_ something [1 .. ]
終了します。[ただし、その状態は使用できなくなることに注意してください。使用する前に、無限のリスト全体を移動する必要があります。したがって、そのようなことを行う場合は、状態に依存する計算を再開する前にput
、新しい状態にする必要があります。]