2017-07-08 6 views
5

Je joue avec les tours de monade Cont décrits here et this SO question.État sur cont. Pourquoi mon état n'est-il pas réinitialisé?

Cette fonction vous permet de « revenir en arrière » plus haut dans le calcul, en prenant un paramètre de sorte que vous pouvez faire les choses différemment:

import Control.Monad.Cont 
import Control.Monad.State.Strict 
import Control.Monad.Writer.Strict 

getCC' :: MonadCont m => a -> m (a,a -> m b) 
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f)) 

Je ces exemples de jouets de transformateurs monade au-dessus de 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 

dans le premier exemple (comme dans la explained liée SO question) les effets de Cont ont « priorité » sur les effets de la WriterT. Lorsque nous avons rétabli le calcul, le journal est perdu:

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

Le second exemple fait exactement la même chose, en utilisant uniquement StateT au lieu de WriterT. Cependant, dans ce cas, le journal est conservé!

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

Quelle est l'explication de cette divergence?

Répondre

4

(je pense que ce n'est pas une réponse tout à fait satisfaisante, mais au moins il devrait clarifier un peu.)

Je crois que cela est dû à la levée de callCC. Dans l'état monade cas, après avoir chassé le lapin dans le trou, nous rencontrons ceci:

liftCallCC :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b 

levage uniforme d'une opération callcc à la nouvelle monade. Cette version revient à l'état d'origine en entrant dans la suite.

liftCallCC' :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b 

Levage in situ d'une opération callCC vers la nouvelle monade. Cette version utilise l'état actuel en entrant dans la suite.

Lequel est pris? Le seul état de conservation:

instance MonadCont m => MonadCont (LazyState.StateT s m) where 
    callCC = LazyState.liftCallCC' callCC 

instance MonadCont m => MonadCont (StrictState.StateT s m) where 
    callCC = StrictState.liftCallCC' callCC 

Que se passe-t-il pour l'auteur monad?

instance (Monoid w, MonadCont m) => MonadCont (LazyWriter.WriterT w m) where 
    callCC = LazyWriter.liftCallCC callCC 

instance (Monoid w, MonadCont m) => MonadCont (StrictWriter.WriterT w m) where 
    callCC = StrictWriter.liftCallCC callCC 

Ah-ha! Non '!

liftCallCC :: Monoid w => CallCC m (a, w) (b, w) -> CallCC (WriterT w m) a b 

Lift une opération de callcc à la nouvelle monade.

Aucun variant conservant l'état n'est trouvé dans la bibliothèque. La variante ci-dessus, au contraire, se trouve défini comme il

liftCallCC callCC f = WriterT $ 
    callCC $ \ c -> 
    runWriterT (f (\ a -> WriterT $ c (a, mempty))) 

Notez le mempty.Si nous avions une opération get, nous pourrions y stocker l '"état actuel", afin qu'il ne soit pas perdu dans le processus, mais si nous avions cela, nous ne serions plus dans la monade écrivain, mais dans l'état.


Notez également que l'empilement des monades dans l'ordre inverse permet d'obtenir ce que nous voulons.

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") 
+0

je me sens le comportement oublier l'état doit être la valeur par défaut, pas seulement pour l'uniformité avec 'WriterT', mais de faire respecter la règle intuitive que les effets de monades internes « ont la priorité » dans un certain sens. – danidiaz