3

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.

asked May 6 at 18:26
0

2 Answers 2

8

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).

answered May 6 at 19:48
Sign up to request clarification or add additional context in comments.

6 Comments

At the moment I find this answer very hard to grok. I'll maybe come back to it when I'll have the tools to understand what it means.
@Enlico If this answer is too much, read only the final code block. It's essentially a complete answer to the question "what API characterizes Maybe?" all on its own.
@DanielWagner, then maybe I've been a bit too vague in my OP :( I've updated it now to clarify where I'm trying to get to.
@Enlico As far as I can tell, the answer is the same. You just use nothing in place of mzero in your code, and you can do the thing you ask for. (OR just use MonadPlus instead of MonadMaybe.)
Then Maybe a can be defined as (forall m. MonadMaybe m => m a)?
|
4

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
answered May 7 at 20:36

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.