What is a clean way to handle one monad in another?

This is the problem of bonding monads. Not in the form of a stack, but in the form of the need to deploy one monad to start an operation inside another.

Two domains: Weblog and application. But keep in mind that the application domain will call additional, just as it is currently calling in the Weblog. Both have their own monad stacks. Both keep track of their condition.

newtype WeblogM a = WeblogM (ReaderT Weblog (ErrorT WeblogError IO) a) deriving (Monad, MonadIO, Reader.MonadReader Weblog, Error.MonadError WeblogError) newtype AppM a = AppM (ReaderT App (EitherT AppError IO) a) deriving ( Functor, Applicative, Monad , MonadReader App, MonadError AppError) 

To start the WeblogM operation inside the AppM function, I found that I need to deploy WeblogM and overwrite it using the following functions:

 runWeblogHere :: forall a. Weblog.Weblog -> Weblog.WeblogM a -> AppM a runWeblogHere weblog action = runIO (left . WeblogError) (Weblog.runWeblog weblog action) runIO :: (e -> EitherT AppError IO a) -> IO (Either ea) -> AppM a runIO handler = AppM . lift . handleT handler . EitherT 

However, this really makes my actual pass-through operations pretty simple:

 getPage :: Weblog.PageId -> AppM Weblog.WikiPage getPage pageid = do App{weblog} <- ask runWeblogHere weblog $ Weblog.getWikiPage pageid 

This bothers me already because I have other monadic libraries that I already know that I'm going to connect to the AppM architecture, and I'm worried about writing the runXHere method, which is really a template, for each of them.

I have a suggestion to create a MonadWeblog class to match WeblogM , in much the same way that MonadReader matches ReaderT . I like this more because I can start isolating the monad cache in my MonadWeblog instance (or, indeed, MonadX ).

+7
haskell monads monad-transformers
source share
1 answer

If we ignore the new types and convert both error transformers to ExceptT , these two monad stacks have a similar structure:

 import Control.Monad import Control.Monad.Trans.Except (ExceptT, catchE) import Control.Monad.Trans.Reader type M env err r = ReaderT env (ExceptT err IO) r 

Using withReaderT and mapReaderT functions, we can determine:

 changeMonad :: (env' -> env) -> (err -> ExceptT err' IO r) -> M env err r -> M env' err' r changeMonad envLens handler = withReaderT envLens . mapReaderT (flip catchE handler) 

Edit: To facilitate the transfer and deployment of new types, we can make them Wrapped instances from the lens library and define a more general conversion function:

 {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TemplateHaskell #-} newtype N1 r = N1 { getN1 :: M (Int,Int) String r } $(makeWrapped ''N1) --instance Wrapped (N1 r) where -- type Unwrapped (N1 r) = M (Int,Int) String r -- _Wrapped' = iso getN1 N1 newtype N2 r = N2 { getN2 :: M Int Char r } $(makeWrapped ''N2) changeMonad' :: (Wrapped (n1 r), Unwrapped (n1 r) ~ M env' err' r, Wrapped (n2 r), Unwrapped (n2 r) ~ M env err r) => (env' -> env) -> (err -> ExceptT err' IO r) -> n2 r -> n1 r changeMonad' envLens handler = view _Unwrapped' . changeMonad envLens handler . view _Wrapped' changeN2N1 :: N2 r -> N1 r changeN2N1 = changeMonad' fst (\c -> throwE [c]) 

Wrapped is a class that states: "I'm actually a new type, here is a general way to add / remove a newtype constructor."

If the lens dependency is too heavy, the newtype package provides similar functionality.

+3
source share

All Articles