Warum wird FunctionalDependency zum Definieren von MonadReader benötigt?

8

Ich habe gerade die Definition der Klasse verstanden MonadReader

class Monad m => MonadReader r m | m -> r where
...

Nachdem ich das Dokument der funktionalen Abhängigkeit in Haskell gelesen habe, kann ich jetzt verstehen, dass | m -> rangegeben wird, dass die Typvariable reindeutig von bestimmt wird m. Ich denke, diese Anforderung ist vernünftig, basierend auf den wenigen typischen Instanzen von MonadReader, die ich bisher gesehen habe (z. B. Reader), aber es scheint mir, dass wir Instanzen wie Readerauch ohne diese funktionale Abhängigkeitsklausel definieren können .

Meine Frage ist, warum wir bei der Definition von MonadReader eine funktionale Abhängigkeit benötigen. Ist dies funktional erforderlich, um MonadReader in einem Sinne zu definieren, dass MonadReader ohne MonadReader nicht ordnungsgemäß definiert werden kann, oder ist es lediglich eine Einschränkung, die Verwendungsmöglichkeiten von MonadReader einzuschränken, damit sich die Instanzen von MonadReader alle auf eine bestimmte erwartete Weise verhalten?

Lifu Huang
quelle
Eine unterhaltsame Übung, wenn diese Fragen gestellt werden, besteht darin, sie mit einer Definition ohne Dep zu schreiben.
Thomas M. DuBuisson
1
Es wird nicht zum Definieren benötigt MonadReader; es wird für die bequeme Verwendung benötigt MonadReader.
Daniel Wagner

Antworten:

4

Es ist erforderlich, dass die Typinferenz auf eine Weise funktioniert, die für den Benutzer bequemer ist.

Ohne den Fundep würde dies beispielsweise nicht kompiliert werden:

action :: ReaderT Int IO ()
action = do
  x <- ask
  liftIO $ print x

Um die obige Kompilierung zu machen, müssten wir schreiben

action :: ReadertT Int IO ()
action = do
  x <- ask :: ReadertT Int IO Int
  liftIO $ print x

Dies liegt daran, dass der Compiler ohne fundep nicht darauf schließen kann, dass xes sich um eine handelt Int. Schließlich kann eine Monade ReadertT Int IOmehrere Instanzen haben

instance MonadReader Int (ReaderT Int IO) where
   ask = ReaderT (\i -> return i)
instance MonadReader Bool (ReaderT Int IO) where
   ask = ReaderT (\i -> return (i != 0))
instance MonadReader String (ReaderT Int IO) where
   ask = ReaderT (\i -> return (show i))
-- etc.

Daher muss der Programmierer eine Anmerkung bereitstellen, die erzwingt x :: Int, oder der Code ist mehrdeutig.

Chi
quelle
2

Dies ist keine wirkliche Antwort, aber für einen Kommentar viel zu lang. Sie haben MonadReaderRecht, dass es möglich ist, die Klasse ohne Fundep zu definieren . Insbesondere bestimmt die Typensignatur jeder Methode jeden Klassenparameter. Es wäre durchaus möglich, eine feinere Hierarchie zu definieren.

class MonadReaderA r m where
  askA :: m r
  askA = readerA id

  readerA :: (r -> a) -> m a
  readerA f = f <$> askA

-- This effect is somewhat different in
-- character and requires special lifting.
class MonadReaderA r m => MonadReaderB r m where
  localB :: (r -> r) -> m a -> m a

class MonadReaderB r m
  => MonadReader r m | m -> r

ask :: MonadReader r m => m r
ask = askA

reader
  :: MonadReader r m
  => (r -> a) -> m a
reader = readerA

local
  :: MonadReader r m
  => (r -> r) -> m a -> m a
local = localB

Das Hauptproblem bei diesem Ansatz besteht darin, dass Benutzer eine Reihe von Instanzen schreiben müssen.

dfeuer
quelle
1

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 menthält r(für häufige Fälle). Lassen Sie mich eine leichtere Definition von Readeras verwenden

newtype Reader r a = Reader {runReader :: r -> a}

Wenn der rParameter ausgewählt ist, können Sie einfach eine Monadeninstanz für definieren Reader r. Das bedeutet, dass in der Typklasse die Definition ersetzt werden msollte 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 askinnerhalb der MonadReaderKlasse 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, askeinen 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 Doublehä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 Intdie funktionale Abhängigkeit sagt solche Art könnte nur eine einzige Instanz haben MonadReadertypeclass , die derjenige, der Verwendungen sein definiert ist Stringalsr

lsmor
quelle