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 (1) 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.
(1) Changing runMaybeT to (runMaybeT . forever) allows to succintly express a computation that can run forever or exit early via mzero.
2 Answers 2
This is an interesting question with two layers:
What does it mean to define an interface for a monad?
To answer the first question I would point to free monads which are exactly those monads that are defined by an interface. The free State monad can be defined like this:
data FreeState s a = Get (s -> FreeState s a) | Put s (FreeState s a) | Pure a
The definition of free monads corresponds directly to the interface. I have named the Get and Put constructors to reflect this, but I don't think I have enough time and space here to explain the full details.
Is there a Monad... class for the Maybe monad?
Now that we have an idea of what it means to define an interface for a monad and how that corresponds to data types, we can look at the Maybe data type:
data Maybe a = Nothing | Just a
Notice how the Just looks like the Pure from our FreeState monad? That must mean the Nothing is our one and only operation of this monad. Indeed, this data type corresponds directly to this interface:
class Monad m => MonadMaybe m where
nothing :: m a
This interface is not that useful on its own. There are a few closely related monad interfaces that have found common use, for example MonadFail (adds an extra string argument to the operation) and MonadPlus/Alternative (corresponds to the list monad).
6 Comments
Maybe?" all on its own.nothing in place of mzero in your code, and you can do the thing you ask for. (OR just use MonadPlus instead of MonadMaybe.)Maybe a can be defined as (forall m. MonadMaybe m => m a)?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 bars 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
Comments
Explore related questions
See similar questions with these tags.