How do I create a monad that allows IO but is not MonadIO?
I am trying to create a monad where only certain I / O functions are allowed. This means that this hypothetical monad cannot MonadIO
and cannot cause liftIO
.
Here is what I have so far, but I am stuck with an instance Monad
for AppM
:
data AppM a = AppM {unwrapAppM :: ReaderT Env (LoggingT IO) a}
instance Functor AppM where
fmap fn appm = AppM $ fmap fn (unwrapAppM appm)
instance Applicative AppM where
pure a = AppM $ pure a
source to share
If you just want to hide the MonadIO
-ness of yourAppM
I would go on and put
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
and change your ad data
to
newtype App a = App {runApp :: ReaderT Env (LoggingT IO) a}
deriving (Functor, Applicative, Monad, MonadReader Env,
, MonadLoggerIO }
So yours App
doesn't matter MonadIO
if you need liftIO
both actions that you can provide to those inside your library, for example
putStrLn :: String -> App () putStrLn = fmap App . liftIO Prelude.putStrLn
Note: liftIO
intended for ReaderT Env (LoggingT IO) ()
, which is then wrapped in App
, and you do not disclose full I / O capabilities.
Update
On the question of how to implement Functor
, Applicative
and Monad
it's just a problem of the wrapping / unpacking:
instance Functor App where
fmap f = App . fmap f . runApp
instance Applicative App where
pure = App . pure
mf <*> mx = App (runApp mf <*> runApp mx)
instance Monad App where
mx >>= f = App $ (runApp mx) >>= (runApp . f)
the last line is the only tricky one - like
>>= :: ReaderT Env (LoggingT IO) a -> (a -> ReaderT Env (LoggingT IO) b) -> ReaderT Env (LoggingT IO) b
but mx :: App a
also f :: a -> App b
, therefore we need
runApp :: App a -> ReaderT Env (LoggingT IO) a
to expand the result type f
to work in an expanded setup, which seems really obvious when writing, but can cause headaches before then.
Update2
I found that someone linked me for a long time (but in the same galaxy) from monad reader Ed Z. Yang - Three Monads (logic, hint, glitch)
source to share