Creo que la fuente de confusión es que en la definición de
class Monad m => MonadReader r m | m -> r where
{- ... -}
Se asume implícitamente que se m
contiene a r
sí mismo (para instancias comunes). Déjame usar una definición más ligera de Reader
como
newtype Reader r a = Reader {runReader :: r -> a}
Cuando r
se elige el parámetro, puede definir fácilmente una instancia de mónada para Reader r
. Eso significa que en la definición de clase de tipo se m
debe sustituir Reader r
. Así que mira cómo la expresión termina siendo:
instance MonadReader r (Reader r) where -- hey!! r is duplicated now
{- ... -} -- The functional dependecy becomes Reader r -> r which makes sense
Pero, ¿por qué necesitamos esto? Mira la definición de ask
dentro de la MonadReader
clase.
class Monad m => MonadReader r m | m -> r where
ask :: m r -- r and m are polymorphic here
{- ... -}
Sin el fun-dep nada podría detenerme por definir ask
una forma de devolver un tipo diferente como el estado. Aún más, podría definir muchas instancias de lector de mónada para mi tipo. Como ejemplo, estas serían definiciones válidas sin 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
{- ... -}
Entonces, si tuviera un valor, val :: ReaderT Int IO Double
¿cuál sería el resultado ask
? Tendríamos que especificar una firma de tipo como se muestra a continuación
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
Además de no tener sentido, no es conveniente especificar el tipo una y otra vez.
Como conclusión, utilizando la definición real de ReaderT
. Cuando tiene algo como que val :: ReaderT String IO Int
la dependencia funcional dice: Tal tipo podría tener solo una instancia única de MonadReader
typeclass que se define como la que usa String
comor