Coroutine monad transformer for suspending and resuming monadic computations

LTS Haskell 17.9:0.9.1
Stackage Nightly 2021-04-15:0.9.1
Latest on Hackage:0.9.1

See all snapshots monad-coroutine appears in

LicenseRef-GPL licensed by Mario Blazevic
This version can be pinned in stack with:monad-coroutine-0.9.1@sha256:d26538bc4dbc9d8fa49dbf558a5f54078f0ebb2ad4a5f268f6b277b71711288c,1292

Module documentation for 0.9.1

Used by 1 package in nightly-2021-04-06(full list with versions):

The monad-coroutine library, implemented by the Control.Monad.Coroutine module, provides a limited coroutine functionality in Haskell. The centerpiece of the approach is the monad transformer Coroutine, which transforms an arbitrary monadic computation into a suspendable and resumable one. The basic definition is simple:

newtype Coroutine s m r = Coroutine {resume :: m (Either (s (Coroutine s m r)) r)}

instance (Functor s, Monad m) => Monad (Coroutine s m) where
  return = Coroutine . return . Right
  t >>= f = Coroutine (resume t >>= either (return . Left . fmap (>>= f)) (resume . f))

Suspension Functors

The Coroutine transformer type is parameterized by a functor. The functor in question wraps the resumption of a suspended coroutine, and it can carry other information as well. Module Control.Monad.Coroutine.SuspensionFunctors exports some useful functors, one of which is Yield:

data Yield x y = Yield x y
instance Functor (Yield x) where
  fmap f (Yield x y) = Yield x (f y)

A coroutine parameterized by this functor is a generator which yields a value every time it suspends. For example, the following function generates the program’s command-line arguments:

genArgs :: Coroutine (Yield String) IO ()
genArgs = getArgs >>= mapM_ yield

The Await functor is dual to Yield; a coroutine that suspends using this functor is a consumer coroutine that on every suspension expects to be given a value before it resumes. The following example is a consumer coroutine that prints every received value to standard output:

printer :: Show x => Coroutine (Await x) IO ()
printer = await >>= print >> printer           

While these two are the most obvious suspension functors, any functor whatsoever can be used as a coroutine suspension functor.

Running a coroutine

After a coroutine suspends, the suspension functor must be unpacked to get to the coroutine resumption. Here’s an example of how the printer example could be run:

printerFeeder :: Show x => [x] -> Coroutine (Await x) IO () -> IO ()
printerFeeder [] _ = return ()
printerFeeder (head:tail) printer = do p <- resume printer
                                       case p of Left (Await p') -> printerFeeder tail (p' head)
                                                 Right result -> return result

Alternatively, you can use the function pogoStick or foldRun to the same effect:

printerFeeder feed printer = liftM snd $ foldRun f feed printer
  where f (head:tail) (Await p) = (tail, p head)
        f []          _         = ([], return ())