Проблема в том, что 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)
только более читабельно.Таким образом, (>> =) позволяет BLOB-объекту полностью оценить.Оценка требуется только в том случае, если k
необходимо проверить fst blob
, чтобы определить, как продолжить, или k a
необходимо проверить snd blob
.
В replicateM r tick
вычисления вычисляются с помощью (>>), поэтому определение k
в (>> =) - 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
, ничто не требует какой-либо оценки, пока не будет получен окончательный snd
после достижения runState
.К тому времени был построен ствол с миллионом слоев.Для оценки этого thunk требуется помещать множество let m' = m+1 in seq m' ((),m')
в стек до тех пор, пока не будет достигнуто начальное состояние, и, если стек достаточно велик для их хранения, они затем будут извлечены и применены.Таким образом, это будет три обхода: 1. создание thunk, 2. отслоение слоев от thunk и помещение их в стек, 3. использование стека.
(>> =) Control.Monad.State.Strict достаточно строг, чтобы заставить seq
s на каждом связывании, таким образом, существует только один обход, нет (нетривиального) блока, и вычисление выполняется в постоянном пространстве.Определение:
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
с, CMSStrict также создает большие блоки, которые могут привести к переполнению стека.Thunks просто немного проще и менее запутан, чем генерируемые CMSLazy в этих обстоятельствах.
С другой стороны, лень CMSLazy допускает другие вычисления, которые невозможны с CMSStrict.Например, CMSLazy предоставляет одну из очень немногих монад, где
take 100 <$> mapM_ something [1 .. ]
заканчивается.[Но знайте, что государство тогда непригодно;прежде чем его можно будет использовать, ему придется пройти через весь бесконечный список.Итак, если вы делаете что-то подобное, прежде чем вы сможете возобновить вычисления, зависящие от состояния, вам нужно put
новое состояние.]