The way I look at MonadState
, for instance, is that any type (or set of types, e.g. ReaderT r m a
) that implements it, must support get
+put
(or alternatively just state
) in order to behave like the State
monad, which is indeed a Monad
, but not only a Monad
, as get
+put
is the API characterizes it.
But then MonadMaybe
would be a class that a monadic stack should implement if it wanted to behave like... a Maybe
? What does that even mean? I mean, Maybe
is Monad
, but what else? It doesn't offer any API that characterizes it, does it?
I've read Edward Kmett's answer on reddit, but I'm not sure I understand. Probably I'm drowing in a inch of water.
What would that typeclass look like?
class Monad m => MonadMaybe m where
...???
Concretely, say I have this
foo :: Monad m => MaybeT (StateT Int (ReaderT Int m)) Int
foo = do
s <- get
r <- ask
if s + r == 0
then mzero -- see note (¹) at the bottom
else return $ s + r
This is a monadic stack which hardcodes the order of the effects provided by State
and Reader
, so it can be run like this
bar :: IO (Maybe Int)
bar = runMaybeT foo `evalStateT` 3 `runReaderT` 3
but not like this
bar :: IO (Maybe Int)
bar = runMaybeT (foo `evalStateT` 3 `runReaderT` 3)
because the monadic layers have to be peeled off from the outside, i.e. in the order MaybeT
->StateT
->ReaderT
.
One way to free bar
from that obligation is to require that m
implements the appropriate typeclasses, like this
foo :: (MonadState Int m, MonadReader Int m) => MaybeT m Int
foo = do
s <- get
r <- ask
if s + r == 0
then mzero
else return $ s + r
Now both bar
implementations above work.
At this point, I can't help but thinking that if I had MonadMaybe
, I could rewrite the signature of foo
like this:
foo :: (MonadMaybe m, MonadState Int m, MonadReader Int m) => m Int
That would mean that bar
could be implemented in any of these ways,
bar :: IO (Maybe Int)
bar = (runMaybeT foo) `evalStateT` 3 `runReaderT` 3 -- MSR
bar = (runMaybeT foo) `runReaderT` 3 `evalStateT` 3 -- MRS
bar = runMaybeT (foo `runReaderT` 3) `evalStateT` 3 -- RMS
bar = runMaybeT (foo `evalStateT` 3) `runReaderT` 3 -- SMR
bar = runMaybeT (foo `runReaderT` 3 `evalStateT` 3) -- RSM
bar = runMaybeT (foo `evalStateT` 3 `runReaderT` 3) -- SRM
where I've used permutations of MSR
to mean the order in which the 3 layers are processed.
(¹) Changing runMaybeT
to (runMaybeT . forever)
allows to succintly express a computation that can run forever or exit early via mzero
.
What Kmett's answer boils down to is that you can rewrite your final foo
signature as:
foo :: (MonadPlus m, MonadState Int m, MonadReader Int m) => m Int
and all your bar
s run fine. MonadMaybe
isn't needed because the interface in MonadPlus
is already sufficient.
Full example:
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Trans.Maybe
foo :: (MonadPlus m, MonadState Int m, MonadReader Int m) => m Int
foo = do
s <- get
r <- ask
if s + r == 0
then mzero
else return $ s + r
main :: IO ()
main = do
print =<< runMaybeT foo `evalStateT` 3 `runReaderT` 3 -- MSR
print =<< runMaybeT foo `runReaderT` 3 `evalStateT` 3 -- MRS
print =<< runMaybeT (foo `runReaderT` 3) `evalStateT` 3 -- RMS
print =<< runMaybeT (foo `evalStateT` 3) `runReaderT` 3 -- SMR
print =<< runMaybeT (foo `runReaderT` 3 `evalStateT` 3) -- RSM
print =<< runMaybeT (foo `evalStateT` 3 `runReaderT` 3) -- SRM