StateT over Cont. Why is my condition not reset?

I play with the Cont monad tricks described here here and this SO question .

This function allows you to "go back" to earlier in the calculation by taking a parameter so that you can do something differently:

 import Control.Monad.Cont import Control.Monad.State.Strict import Control.Monad.Writer.Strict getCC' :: MonadCont m => a -> m (a,a -> mb) getCC' x0 = callCC (\c -> let fx = c (x, f) in return (x0, f)) 

I have these toy examples of monad transformers on top of Cont :

 foo :: WriterT String (Cont String) () foo = do (stop,loop) <- getCC' False if stop then do tell "bbb" else do tell "aaa" loop True foo' :: StateT String (Cont String) () foo' = do (stop,loop) <- getCC' False if stop then do modify $ \s -> s ++ "bbb" else do modify $ \s -> s ++ "aaa" loop True 

In the first example (as explained in a related SO question), Cont effects take precedence over WriterT effects. When we reset, the log is lost:

 *Main> print $ runCont (execWriterT foo) id "bbb" 

The second example does the same, only using StateT instead of WriterT . However, in this case, the log is saved!

 *Main> print $ runCont (execStateT foo' "") id "aaabbb" 

What is the explanation for this discrepancy?

+5
source share
1 answer

(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") 
+4
source

Source: https://habr.com/ru/post/1269634/


All Articles