Repros:
cabal repl --build-depends=mtl-prelude,transformers
λ> import Data.Mayb
λ> import Control.Monad
λ> import Control.Monad.Trans.Identity
λ> import MTLPrelude
λ> :{
ghci| maybeQuit :: MonadPlus m => Maybe Char -> MaybeT m (Maybe Char)
ghci| maybeQuit key = do
ghci| case key of
ghci| Just 'q' -> mzero
ghci| Just '\ESC' -> mzero
ghci| _ -> return key
ghci| :}
λ> runIdentityT $ runMaybeT $ maybeQuit (Just 'c')
Just (Just 'c')
λ> runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
Nothing
So far so good.
But then:
λ> (runIdentityT $ runMaybeT $ maybeQuit (Just 'q')) == Nothing
False
λ> isNothing (runIdentityT $ runMaybeT $ maybeQuit (Just 'q'))
False
What?!
I do see that expression is a bit polymorphic,
λ> :t runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
:: MonadPlus f => f (Maybe (Maybe Char))
but I'm not sure at all how this can imply that something printed as Nothing
could ever be other than (==) Nothing
.
For context, I'm running maybeQuit
in a monadic stack (there's MaybeT
and other transformers in it) with a IO
at the bottom, and it is working as expected, but it's implementations, hence the type, does not require the power of IO
, so I was trying to test it in a monad other than IO
, and here's how I came to ask this question.
In hindsight I do start seeing something else I don't quite understand: I see :t runMaybeT $ maybeQuit (Just 'q')
is MonadPlus m => m (Maybe (Maybe Char))
, which is what I expect, but then applying runIdentityT
to that gives type MonadPlus f => f (Maybe (Maybe Char)
, which is the same thing, so I must be misunderstanding the meaning/purpose of IdentityT
, and can't help but thinking this is the whole reason I don't get what I think I should get in the original example.
In light of the above reasoning, I've tried to force m === []
in the expression runMaybeT $ maybeQuit (Just 'c')
by applying head
to it, and the result is more what I expect:
λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'q'))
True
λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'c'))
False
but I still don't understand what's wrong with using runIdentityT
.
To understand what's going on, consider this much simpler example:
ghci> import Data.Maybe
ghci> weird = return Nothing
ghci> weird
Nothing
ghci> weird == Nothing
False
ghci> isNothing weird
False
ghci> :t weird
weird :: Monad m => m (Maybe a)
ghci>
When you just enter weird
, GHCi defaults m
to be IO
(so the overall type is IO (Maybe a)
), and then evaluates the IO action for you, which results in Nothing
. When you enter weird == Nothing
or isNothing weird
, m
is forced to be Maybe
(so the overall type is Maybe (Maybe a)
), so return Nothing
becomes Just Nothing
, which is different than Nothing
.
Your example has a couple of extra layers of indirection, but at the end of the day, the same thing is happening. runIdentityT
was a red herring; your expression is so polymorphic that it doesn't do anything at all where it is.
If you're confused as to why your expression is basically the same as return Nothing
, remember that the mzero
you're using is defined like this:
instance (Monad m) => MonadPlus (MaybeT m) where
mzero = MaybeT (return Nothing)
Not this:
instance (MonadPlus m) => MonadPlus (MaybeT m) where
mzero = MaybeT mzero