这是因为Control.Monad.State
再出口Control.Monad.State.Lazy
。如果您导入, Control.Monad.State.Strict
,两者都会以这种方式溢出。
它溢出的原因是严格的,State
或者需要递归地运行动作时间,然后才能构建列表。说得通俗一点,就是要把它所复制的所有动作的“效果”“组合”成一个巨大的“效果”。“结合”和“效果”这两个词非常模糊,可以表示无数种不同的事物,但它们是我们谈论这些抽象事物时所能得到的最好的东西。在几乎所有的 monad 选择中,具有较大值的最终都会溢出堆栈。奇怪的是,它没有懒惰。IO
replicateM
iterations
replicateM
replicateM
State
要了解为什么它不会因 lazy 溢出State
,您需要查看(>>=)
for lazyState
和replicateM
. 以下定义已大大简化,但它们反映了说明其工作原理所需的细节。
newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return x = State $ \s -> (x, s)
x >>= f = State $ \s -> let (a, s') = runState x s in runState (f a) s'
replicateM :: Monad m => Int -> m a -> m [a]
replicateM 0 _ = return []
replicateM n mx | n < 0 = error "don't do this"
| otherwise =
mx >>= \x -> replicateM (n - 1) mx >>= \xs -> return (x:xs)
所以首先,看replicateM
。请注意,当n
大于 0 时,它是对 的调用(>>=)
。所以 的行为replicateM
密切取决于做什么(>>=)
。
当您查看 时(>>=)
,您会看到它生成了一个状态转换函数,该函数将状态转换函数的结果绑定到x
let 绑定中,然后返回转换函数的结果,该结果是f
从该绑定应用于参数的结果。
好吧,那句话很清楚,但它真的很重要。让我们暂时看一下 lambda 内部。查看函数(>>=)
创建的结果,您会看到let {something to do with x} in {something to do with f and the results of the let binding}
. 这对于惰性评估很重要。这意味着,如果特定函数允许,它可能会在评估 时忽略它x
,或者可能忽略它的一部分。在惰性的情况下,这意味着它可能能够延迟计算未来的状态值,如果可以在查看状态之前生成构造函数。(>>=)
f
State
f
事实证明,这是允许它工作的原因。replicateM
组装对 的调用的特殊方式,它会产生一个函数,该函数会在检查传递给它们的状态之前(>>=)
生成构造函数。(:)
如果从不检查最终状态,这允许对列表进行增量处理。如果您查看最终状态,那会破坏增量功能的能力,因为最终状态需要完成所有工作来计算它。但是您的使用evalState
导致最终状态未经检查就被丢弃,因此评估可以自由地逐步进行。