Reputation: 6793
I'm trying to create a monad where only specific IO functions are allowed. This means that this hypothetical monad cannot be a MonadIO
and cannot allow liftIO
to be called.
Here's what I have till now, but I'm stuck with the Monad
instance 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
Upvotes: 3
Views: 240
Reputation: 15959
If you just want to hide the MonadIO
-ness of your AppM
I would go on and put in a
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
and change the data
declaration to
newtype App a = App {runApp :: ReaderT Env (LoggingT IO) a}
deriving (Functor, Applicative, Monad, MonadReader Env,
, MonadLoggerIO }
Thus your App
is no MonadIO
if you need to liftIO
like actions you can provide those inside of your library like
putStrLn :: String -> App ()
putStrLn = fmap App . liftIO Prelude.putStrLn
Note: the liftIO
is for ReaderT Env (LoggingT IO) ()
which then is wrapped into App
, and you do not expose full IO capabilities.
Regarding the question how to implement Functor
, Applicative
and Monad
it is just the mere task of wrapping/unwrapping:
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 - as
>>= :: ReaderT Env (LoggingT IO) a -> (a -> ReaderT Env (LoggingT IO) b) -> ReaderT Env (LoggingT IO) b
but mx :: App a
and f :: a -> App b
so we need
runApp :: App a -> ReaderT Env (LoggingT IO) a
to unwrap the result type of f
to work in the unwrapped setting - which seems really obvious when written down, but can cause some headache, before that.
I found that paper someone linked me a long long time a go (but in the same galaxy) from the monad reader Ed Z. Yang - Three Monads (Logic, Prompt, Failure)
Upvotes: 9