Search code examples
haskelltypesmonad-transformers

Building a monad around Streaming & Reader


I am trying to build a Monad instance of the following type:

data CmdY ρ η ω = CmdY (Reader ρ ((Stream (Of CmdSpec) η ω)))

where Stream and Of come from Streaming in the streaming package.

I have successfully written Functor Applicative instances:

instance (Monad η) => Functor (CmdY ρ η) where
  fmap :: (α -> β) -> CmdY ρ η α -> CmdY ρ η β
  fmap f (CmdY a) = CmdY $ (fmap f) <$> a

instance (Monad η) => Applicative (CmdY ρ η) where
  pure    = CmdY . pure . return

  (<*>) :: CmdY ρ η (α -> β) -> CmdY ρ η α -> CmdY ρ η β
  (CmdY f) <*> (CmdY a) = let ff = (<*>) <$> f
                           in CmdY $ ff <*> a

But I'm going round in circles trying to implement the bind (>>=) of the Monad.

I have a function to evaluate a CmdY and give me a stream & result:

runCmdY :: (MonadIO η, MonadIO μ, MonadReader (ProcExecCtxt μ) ψ) =>
           CmdY (ProcExecCtxt μ) η ω -> ψ (Stream (Of CmdSpec) η ω)

But given a bind of the form a >>= f, a is of type CmdY (ProcExecCtxt η) η α while f is of type α -> CmdY (ProcExecCtxt η) η β, I need to get something of type α to feed to my f, and I am failing to get there.

ProcExecCtxt m here is an execution context; it provides a means to evaluate cmds within the monad m.

I'm sure I'm missing something obvious (or at least, I'm hoping it will be obvious once I see it); but I'd grateful for any pointers.

Thanks,


Solution

  • Reader ρ (Stream (Of CmdSpec) η ω)
    

    is really just

     ρ -> Stream (Of CmdSpec) η ω
    

    There's a third way to spell that type that probably makes more sense in this context:

    ReaderT ρ (Stream (Of CmdSpec) η) ω
    

    using Control.Monad.Trans.Reader.ReaderT (also exported by Control.Monad.Reader), a monad transformer defined

    newtype ReaderT e m a = ReaderT
      { runReaderT :: e -> m a }
    

    Since ReaderT e is a monad transformer, ReaderT e m is a monad whenever m is. Therefore, it will provide all the instances you want for free, and a couple more. Indeed, using

    {-# language GeneralizedNewtypeDeriving #-}
    

    you can write

    newtype CmdY ρ η ω = CmdY
      { unCmdY :: ReaderT ρ (Stream (Of CmdSpec) η) ω }
      deriving (Functor, Applicative, Monad
               , Alternative, MonadPlus, MonadReader ρ)
    

    Or, if you like, you can define the instances manually by wrapping and unwrapping CmdY:

    instance Monad η => Functor (CmdY ρ η) where
      fmap f (CmdY m) = CmdY (fmap f m)
    
    instance Monad η => Applicative (CmdY ρ η) where
      pure = CmdY . pure
      CmdY fs <*> CmdY xs = CmdY (fs <*> xs)
    
    instance Monad η => Monad (CmdY ρ η) where
      CmdY m >>= f = CmdY $ m >>= unCmdY . f
    

    CmdY ρ is itself a monad transformer:

    import Control.Monad.Trans.Class
    
    instance MonadTrans (CmdY ρ) where
      lift = CmdY . lift . lift