Ich denke, die Quelle der Verwirrung ist die in der Definition von
class Monad m => MonadReader r m | m -> r where
{- ... -}
Es wird implizit angenommen, dass es sich selbst m
enthält r
(für häufige Fälle). Lassen Sie mich eine leichtere Definition von Reader
as verwenden
newtype Reader r a = Reader {runReader :: r -> a}
Wenn der r
Parameter ausgewählt ist, können Sie einfach eine Monadeninstanz für definieren Reader r
. Das bedeutet, dass in der Typklasse die Definition ersetzt werden m
sollte Reader r
. Schauen Sie sich also an, wie der Ausdruck endet:
instance MonadReader r (Reader r) where -- hey!! r is duplicated now
{- ... -} -- The functional dependecy becomes Reader r -> r which makes sense
Aber warum brauchen wir das? Schauen Sie sich die Definition ask
innerhalb der MonadReader
Klasse an.
class Monad m => MonadReader r m | m -> r where
ask :: m r -- r and m are polymorphic here
{- ... -}
Ohne die fun-dep könnte mich nichts daran hindern, ask
einen anderen Typ als Zustand zurückzugeben. Darüber hinaus konnte ich viele Instanzen von Monadenlesern für meinen Typ definieren. Dies wären beispielsweise gültige Definitionen ohne 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
{- ... -}
Wenn ich also einen Wert val :: ReaderT Int IO Double
hätte, was wäre das Ergebnis von ask
. Wir müssten eine Typensignatur wie unten angeben
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
Abgesehen davon, dass es sinnlos ist, ist es nicht überzeugend, den Typ immer wieder zu spezifizieren.
Als Schlussfolgerung unter Verwendung der tatsächlichen Definition von ReaderT
. Wenn Sie so etwas wie haben val :: ReaderT String IO Int
die funktionale Abhängigkeit sagt solche Art könnte nur eine einzige Instanz haben MonadReader
typeclass , die derjenige, der Verwendungen sein definiert ist String
alsr