{-# LANGUAGE PackageImports, FlexibleInstances, MultiParamTypeClasses #-}
-- | A co-routine monad.
module Coroutine (
        CoroutineT(..),
--        CoroutineT(runCoroutineT),
        Result(..),
        yield
    ) where

import Control.Monad
import "mtl" Control.Monad.Trans


data Result i o m a = Yield o (i -> CoroutineT i o m a) | Result a

-- | Co-routine monad transformer
--
--   * i = input value returned by yield
--
--   * o = output value, passed to yield
--
--   * m = next monad in stack
--
--   * a = monad return value
data CoroutineT i o m a = CoroutineT {
        runCoroutineT :: m (Result i o m a)
    }

instance Monad m => Functor (CoroutineT i o m) where
    fmap = liftM

instance Monad m => Monad (CoroutineT i o m) where
    return a = CoroutineT $ return $ Result a
    f >>= g = CoroutineT $ do
        res1 <- runCoroutineT f
        case res1 of
            Yield o c -> return $ Yield o (\i -> c i >>= g)
            Result a  -> runCoroutineT (g a)
    -- Pass fail to next monad in the stack
    fail err = CoroutineT $ fail err

instance MonadTrans (CoroutineT i y) where
    lift m = CoroutineT $ do
        r <- m
        return $ Result r

instance MonadIO m => MonadIO (CoroutineT i o m) where
    liftIO m = CoroutineT $ do
        r <- liftIO m
        return $ Result r

-- | Suspend processing, returning a @o@ value and a continuation to the caller
yield :: Monad m => o -> CoroutineT i o m i
yield o = CoroutineT $ return $ Yield o (\i -> CoroutineT $ return $ Result i)

