Eu acho que a fonte de confusão é que, na definição de
class Monad m => MonadReader r m | m -> r where
{- ... -}
É implicitamente assumido que se m
contém r
(para instâncias comuns). Deixe-me usar um definiton mais claro de Reader
como
newtype Reader r a = Reader {runReader :: r -> a}
Quando o r
parâmetro é escolhido, você pode definir facilmente uma instância de mônada para Reader r
. Isso significa que na definição de classe de tipo m
deve ser substituída Reader r
. Então, veja como a expressão acaba sendo:
instance MonadReader r (Reader r) where -- hey!! r is duplicated now
{- ... -} -- The functional dependecy becomes Reader r -> r which makes sense
Mas por que precisamos disso? Veja a definição de ask
dentro da MonadReader
classe.
class Monad m => MonadReader r m | m -> r where
ask :: m r -- r and m are polymorphic here
{- ... -}
Sem a diversão-dep nada poderia me parar para definir ask
um tipo diferente de estado. Ainda mais, eu poderia definir muitas instâncias do leitor de mônada para o meu tipo. Como exemplo, isso seria definições válidas sem func-dep
instance MonadReader Bool (Reader r) where
-- ^^^^ ^
-- | |- This is state type in the user defined newtype
-- |- this is the state type in the type class definition
ask :: Reader r Bool
ask = Reader (\_ -> True) -- the function that returns True constantly
{- ... -}
instance MonadReader String (Reader r) where
-- ^^^^^^ ^
-- | |- This is read-state type in the user defined newtype
-- |- this is the read-state type in the type class definition
ask :: Reader r String
ask = Reader (\_ -> "ThisIsBroken") -- the function that returns "ThisIsBroken" constantly
{- ... -}
Então, se eu tivesse um valor val :: ReaderT Int IO Double
qual seria o resultado ask
. Precisamos especificar uma assinatura de tipo como abaixo
val :: Reader Int Double
val = do
r <- ask :: Reader Int String
liftIO $ putStrLn r -- Just imagine you can use liftIO
return 1.0
> val `runReader` 1
"ThisIsBroken"
1.0
val :: Reader Int Double
val = do
r <- ask :: Reader Int Bool
liftIO $ print r -- Just imagine you can use liftIO
return 1.0
> val `runReader` 1
True
1.0
Além de ser insensato, não é conveniente especificar o tipo repetidamente.
Como conclusão, usando a definição real de ReaderT
. Quando você tem algo parecido com val :: ReaderT String IO Int
a dependência funcional, diz: Esse tipo pode ter apenas uma instância única da MonadReader
classe, definida como aquela que usa String
comor