(I feel that this is not a completely satisfactory answer, but at least it should clarify a little.)
I believe this is due to the cancellation of callCC . In the case of the state monad, chasing a rabbit down a hole, we come across this:
liftCallCC :: CallCC m (a, s) (b, s) -> CallCC (StateT sm) ab
Uniform raising of operation callCC to the new monad. This version returns to its original state when entering a continuation.
liftCallCC' :: CallCC m (a, s) (b, s) -> CallCC (StateT sm) ab
On-site operation callCC rise to the new monad. This version uses the current state when entering a continuation.
Which one? One persistent state:
instance MonadCont m => MonadCont (LazyState.StateT sm) where callCC = LazyState.liftCallCC' callCC instance MonadCont m => MonadCont (StrictState.StateT sm) where callCC = StrictState.liftCallCC' callCC
What happens to a monad writer?
instance (Monoid w, MonadCont m) => MonadCont (LazyWriter.WriterT wm) where callCC = LazyWriter.liftCallCC callCC instance (Monoid w, MonadCont m) => MonadCont (StrictWriter.WriterT wm) where callCC = StrictWriter.liftCallCC callCC
Ah ha! No ' !
liftCallCC :: Monoid w => CallCC m (a, w) (b, w) -> CallCC (WriterT wm) ab
Lift operation callCC into the new monad.
The option to save state is not saved in the library. Instead, the option found above is defined as
liftCallCC callCC f = WriterT $ callCC $ \ c -> runWriterT (f (\ a -> WriterT $ c (a, mempty)))
Pay attention to mempty . If we had a get operation, we could save the “current state” there so that it would not be lost in the process, but if we had what we would no longer be in the monad of the writer, but in the state.
Also note that stacking monads in the opposite order provides what we want.
bar :: ContT String (Writer String) () bar = do (stop,loop) <- getCC' False if stop then do lift $tell "bbb" else do lift $ tell "aaa" loop True -- > runWriter (runContT bar (const $ pure "")) -- ("","aaabbb")