Suspend/resume computation using Cont monad and callCC

Hi. I have two functions f and g, and i want them to execute in following order: first function f runs, then suspends and passes control to function g. Function g runs, then suspends and "unpauses" function f. Function f finishes and passes control to function g, which also finishes. Here is illustration ('o' means start of function, dot means suspend and pass control to other function, 'x' means end of function): f g o f-1 | v .-->o | g-1 v .<--. | f-2 v x-->. | g-2 v <----------x (to caller) I want to implement this using Cont monad and callCC. And here is my implementation: import Data.Monoid import Control.Monad.Cont import Control.Monad.Writer type M r = Cont r fM :: M r [String] fM = do let xs' = "I'm in f-1" : [] (ys, k') <- callCC (gM xs') let ys' = "I'm in f-2" : ys zs <- k' ys' let zs' = "I'm in f-3" : zs return zs' gM :: [String] -> (([String], [String] -> M r [String]) -> M r [String]) -> M r ([String], [String] -> M r [String]) gM xs k = do let xs' = "I'm in g-1" : xs ys <- callCC (curry k xs') let ys' = "I'm in g-2" : ys return (ys', \_ -> return ys') type T r = ContT r (Writer String) fT :: T r () fT = do lift $ tell "I'm in f-1\n" k' <- callCC gT lift $ tell "I'm in f-2\n" k' undefined lift $ tell "I'm in f-3\n" gT :: ((() -> T r ()) -> T r ()) -> T r (() -> T r ()) gT k = do lift $ tell "I'm in g-1\n" callCC k lift $ tell "I'm in g-2\n" return (\_ -> return ()) First pair (fM and gM) uses monad result to track execution order, second pair (fT and gT) uses Writer monad. But the tracks produced by these pairs differ: *Main> runCont fM id ["I'm in f-3","I'm in g-2","I'm in f-2","I'm in g-1","I'm in f-1"] *Main> putStr . snd . runWriter . flip runContT return $ fT I'm in f-1 I'm in g-1 I'm in f-2 I'm in g-2 I'm in f-2 <----- Why am i here? I'm in f-3 fM/gM pair produces exactly the track, which i expect (see illustration above, though 'f-3' section does not shown there). But fT/gT pair after 'g-2' section returns to "before f-2" point in function f. And i don't understand why. Thus, my question is why does fT/gT work so? And why do results from these pairs differ? -- Dmitriy Matrosov

Dmitriy Matrosov
I have two functions f and g, and i want them to execute in following order: first function f runs, then suspends and passes control to function g. Function g runs, then suspends and "unpauses" function f. Function f finishes and passes control to function g, which also finishes. Here is illustration ('o' means start of function, dot means suspend and pass control to other function, 'x' means end of function):
[...]
I want to implement this using Cont monad and callCC.
Not directly answering your question, but what you need is called coroutines, and there are better monads for that purpose. This is how the Cont monads are defined: newtype Cont r a = Cont ((a -> r) -> r) But what you really need here is called a Coroutine monad: newtype Coroutine f a = Coroutine (Either (f (Coroutine f a)) a) Don't worry about that scary type, because if you look closely you will find that this is just Free as defined in the 'free' package: data Free f a = Free (f (Free f a)) | Pure a This is how it works: The computation either results in a value (Pure) or it returns a way to continue the computation wrapped in `f` (Free): Free (Identity (Pure 15)) This computation suspends with the continuation "Pure 15". If you continue it, it will result in 15. Of course there are some helper functions to ease defining continuations: liftF (Identity 15) So first you need a functor. The monad-coroutine package has coined the term "suspension functor" for this particular purpose. It captures the nature of the suspension. As you saw the Identity functor allows you to suspend and resume: type Suspend = Identity suspend :: Free Suspend () suspend = liftF (Suspend ()) or even more generally: suspend :: (Applicative f) => Free f () suspend = liftF (pure ()) You can use this in a computation: doStuff suspend doOtherStuff suspend return 15 This returns to the controller and allows it to resume the computation if it wishes to: loop :: Free Suspend Integer -> IO Integer loop (Pure x) = return x loop (Free (Identity k)) = do putStrLn "Suspended." loop k You can also define an abortion functor (predefined in Data.Functor.Constant from the "transformers" package): newtype Constant r a = Constant r deriving (Functor) abort :: r -> Free (Constant r) a abort = Free . Constant You will find that in a loop you don't receive a continuation, but instead an abortion value, much like in a Cont computation that ignores its continuation: loop :: Free (Constant Integer) Integer -> IO Integer loop (Pure x) = putStrLn "Completed" >> return x loop (Free (Constant x)) = do putStrLn ("Aborted with: " ++ show x) return x Another possibility is a functor to request values of a certain type: type Request = (->) request :: Free (Request e) a request = Free Pure Now the controlling loop has to supply values when requested to do so: comp :: Free (Request String) Integer comp = do x <- fmap read request y <- if x /= 15 then fmap read request else return 5 return (x + y) loop :: Free (Request String) Integer -> IO Integer loop (Pure x) = return x loop (Free k) = do putStrLn "Gimme something:" getLine >>= loop . k Optionally add a prompt: data Prompt e a = Prompt String (e -> a) deriving (Functor) prompt :: String -> Free (Prompt e) e prompt p = Free (Prompt p Pure) loop :: Free (Prompt String) Integer -> IO Integer loop (Pure x) = return x loop (Free (Prompt p k)) = do putStrLn p getLine >>= loop . k With a type system extension you can even request arbitrary IO actions: data Run a = forall b. Run (IO b) (b -> a) requestIO :: IO a -> Free Run a requestIO c = Free (Run c Pure) loop :: Free Run Integer -> IO Integer loop (Pure x) = return x loop (Free (Run c k)) = do putStrLn "IO action requested." c >>= loop . k And you can yield values: type Yield = (,) yield :: v -> Free (Yield v) () yield x = Free (x, Pure ()) loop :: Free (Yield String) Integer -> IO Integer loop (Pure x) = return x loop (Free (str, k)) = do putStrLn ("Yielded: " ++ str) loop k Or both request and yield (comonad-transformers package): type MySusp v e = Coproduct (Yield v) (Request e) yield :: v -> Free (MySusp v e) () yield x = Free . Coproduct . Left $ (x, Pure ()) request :: Free (MySusp v e) e request = Free . Coproduct . Right $ Pure loop :: Free (MySusp String String) Integer -> IO Integer loop (Pure x) = return x loop (Free (Coproduct f)) = case f of Left (x, k) -> do putStrLn ("Yielded " ++ x) loop k Right k -> do putStrLn "Requested." getLine >>= loop . k There are many more ways to use Free, but this should give you the basic building blocks. I hope it helps. Greets, Ertugrul -- Not to be or to be and (not to be or to be and (not to be or to be and (not to be or to be and ... that is the list monad.

The resumption monad is even simpler, unfortunately I'm not aware of any
beginner level tutorials.
William Harrison at the University of Missouri has some papers introducing
resumption monads but the presentations then move very quickly.
On 12 March 2013 11:53, Ertugrul Söylemez
Not directly answering your question, but what you need is called coroutines, and there are better monads for that purpose. This is how the Cont monads are defined:
newtype Cont r a = Cont ((a -> r) -> r)
But what you really need here is called a Coroutine monad:
newtype Coroutine f a = Coroutine (Either (f (Coroutine f a)) a)
Don't worry about that scary type, because if you look closely you will find that this is just Free as defined in the 'free' package:
data Free f a = Free (f (Free f a)) | Pure a

On Tue, 12 Mar 2013 12:53:37 +0100 Ertugrul Söylemez
wrote: Dmitriy Matrosov
wrote: I have two functions f and g, and i want them to execute in following order: first function f runs, then suspends and passes control to function g. Function g runs, then suspends and "unpauses" function f. Function f finishes and passes control to function g, which also finishes. Here is illustration ('o' means start of function, dot means suspend and pass control to other function, 'x' means end of function):
[...]
I want to implement this using Cont monad and callCC.
Not directly answering your question, but what you need is called coroutines, and there are better monads for that purpose. This is how the Cont monads are defined:
newtype Cont r a = Cont ((a -> r) -> r)
But what you really need here is called a Coroutine monad:
newtype Coroutine f a = Coroutine (Either (f (Coroutine f a)) a)
Don't worry about that scary type, because if you look closely you will find that this is just Free as defined in the 'free' package:
data Free f a = Free (f (Free f a)) | Pure a
On Tue, 12 Mar 2013 17:54:16 +0000 Stephen Tetley
wrote: The resumption monad is even simpler, unfortunately I'm not aware of any beginner level tutorials.
William Harrison at the University of Missouri has some papers introducing resumption monads but the presentations then move very quickly.
Thanks for suggestions! I'll try them (though, i suppose, to understand Free i should read at least something about category theory first). Anyway, i think, that i understand why my implementation works so and can explain it. First, i want to remind the implementation of callCC (omiting Cont wrapper): callCC f = \k -> let g x = \_ -> k x in (f g) k So, actually, callCC provides to function f continuation to monad following callCC itself. This will be the key in my explanation. Let's start with first question and explain how fT/gT pair works. Here is the code from my previous mail with line-numbers: type T r = ContT r (Writer String) 1 fT :: T r () 2 fT = do 3 lift $ tell "I'm in f-1\n" 4 k' <- callCC gT 5 lift $ tell "I'm in f-2\n" 6 k' undefined 7 lift $ tell "I'm in f-3\n" 8 9 gT :: ((() -> T r ()) -> T r ()) -> T r (() -> T r ()) 10 gT k = do 11 lift $ tell "I'm in g-1\n" 12 callCC k 13 lift $ tell "I'm in g-2\n" 14 return (\_ -> return ()) And here is illustrations of first part of execution: | v 3 'f-1' 4 k' <- callCC gT <-------------------------------- \ | -------> 11 'g-1' | 12 callCC k | / | 5 'f-2' <-------- | 6 k' -------------> 13 'g-2' | 14 return (\_ -> return ()) --- I.e. after point 'f-1' function gT is called with continuation k to line 5. Then after point 'g-1' function gT calls this continuation k and execution jumps back to line 5 in function f. But because continuation k have been called in callCC, callCC throws continuation k' to line 13 (in function g) into continuation k. Then after point 'f-2' function f calls continuation k' to line 13. Function g resumes execution and finishes. But what is the next continuation now? The one supplied by (callCC gT) ! And this is again continuation to line 5 in function f. Here i proceed to second illustration: 3 'f-1' 4 k' <- callCC gT <--------------------------------- 5 'f-2' | 6 k' 13 'g-2' | 7 'f-3' 14 return (\_ -> return ()) --- So, function f executes again from point 'f-2'. And meet continuation k' again, but now k' is continuation returned by function gT at line 14. I.e. it is just (\_ -> return ()). Thus, it does nothing and i proceed to point 'f-3'. This explains track produced by Writer monad. But there is one more question: why track produced using monad result differs? Here is the code from my previous mail: type M r = Cont r fM :: M r [String] fM = do let xs' = "I'm in f-1" : [] (ys, k') <- callCC (gM xs') let ys' = "I'm in f-2" : ys zs <- k' ys' let zs' = "I'm in f-3" : zs return zs' gM :: [String] -> (([String], [String] -> M r [String]) -> M r [String]) -> M r ([String], [String] -> M r [String]) gM xs k = do let xs' = "I'm in g-1" : xs ys <- callCC (curry k xs') let ys' = "I'm in g-2" : ys return (ys', \_ -> return ys') And if you "trace" its execution in the same manner, you'll notice the answer: result of monad fM actually determined by call of continuation k', which occurs during "second" function fM run. And during this "second" run continuation k' will be (\_ -> return ys'), where ys' is from function gM. But when ys' had been evaluated in function gM, second pass through 'f-2' not yet happen! That's why it is missed from the result as well. Ugh, well.. that was useless, but still so fascinating :-) -- Dmitriy Matrosov
participants (3)
-
Dmitriy Matrosov
-
Ertugrul Söylemez
-
Stephen Tetley