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.