[Haskell-beginners] Suspend/resume computation using Cont monad and callCC
Ertugrul Söylemez
es at ertes.de
Tue Mar 12 12:53:37 CET 2013
Dmitriy Matrosov <sgf.dma at gmail.com> 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
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.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: not available
URL: <http://www.haskell.org/pipermail/beginners/attachments/20130312/ac0c98a9/attachment.pgp>
More information about the Beginners
mailing list