Reproduções:
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
Até agora tudo bem.
Mas então:
λ> (runIdentityT $ runMaybeT $ maybeQuit (Just 'q')) == Nothing
False
λ> isNothing (runIdentityT $ runMaybeT $ maybeQuit (Just 'q'))
False
O que?!
Eu vejo que a expressão é um pouco polimórfica,
λ> :t runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
:: MonadPlus f => f (Maybe (Maybe Char))
mas não tenho certeza de como isso pode implicar que algo impresso como Nothing
poderia ser diferente de (==) Nothing
.
Para contextualizar, estou executando maybeQuit
em uma pilha monádica (há MaybeT
e outros transformadores nela) com um IO
na parte inferior, e está funcionando como esperado, mas suas implementações, portanto o tipo, não requerem o poder de IO
, então eu estava tentando testá-lo em uma mônada diferente de IO
, e foi assim que cheguei a fazer essa pergunta.
Em retrospecto, começo a ver outra coisa que não entendo muito bem: vejo :t runMaybeT $ maybeQuit (Just 'q')
is MonadPlus m => m (Maybe (Maybe Char))
, que é o que espero, mas, ao aplicar runIdentityT
a isso, obtenho o tipo MonadPlus f => f (Maybe (Maybe Char)
, que é a mesma coisa, então devo estar entendendo mal o significado/propósito de IdentityT
, e não consigo deixar de pensar que essa é a razão pela qual não obtenho o que acho que deveria obter no exemplo original.
À luz do raciocínio acima, tentei forçar m === []
a expressão runMaybeT $ maybeQuit (Just 'c')
aplicando- head
a a ela, e o resultado é mais o que eu esperava:
λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'q'))
True
λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'c'))
False
mas ainda não entendi o que há de errado em usar runIdentityT
.
Para entender o que está acontecendo, considere este exemplo muito mais simples:
Quando você apenas insere
weird
, o GHCi assume como padrãom
serIO
(então o tipo geral éIO (Maybe a)
), e então avalia a ação de IO para você, o que resulta emNothing
. Quando você insereweird == Nothing
ouisNothing weird
,m
é forçado a serMaybe
(então o tipo geral éMaybe (Maybe a)
), entãoreturn Nothing
se tornaJust Nothing
, o que é diferente deNothing
.Seu exemplo tem algumas camadas extras de indireção, mas no final das contas, a mesma coisa está acontecendo.
runIdentityT
era uma pista falsa; sua expressão é tão polimórfica que não faz nada onde está.Se você está confuso sobre o porquê de sua expressão ser basicamente a mesma que
return Nothing
, lembre-se de que o quemzero
você está usando é definido assim:Não isso: