Was ist indizierte Monade?

98

Was ist indizierte Monade und was ist die Motivation für diese Monade?

Ich habe gelesen, dass es hilft, die Nebenwirkungen im Auge zu behalten. Aber die Typensignatur und Dokumentation führt mich nirgendwo hin.

Was wäre ein Beispiel dafür, wie es helfen kann, Nebenwirkungen im Auge zu behalten (oder ein anderes gültiges Beispiel)?

Sibi
quelle

Antworten:

123

Wie immer ist die Terminologie, die die Leute verwenden, nicht ganz konsistent. Es gibt eine Vielzahl von Monaden, die von Monaden inspiriert sind, aber streng genommen nicht ganz so sind. Der Begriff "indizierte Monade" ist einer von mehreren Begriffen (einschließlich "monadisch" und "parametrisierter Monade" (Atkeys Name für sie)) von Begriffen, die zur Charakterisierung eines solchen Begriffs verwendet werden. (Eine andere solche Vorstellung, wenn Sie interessiert sind, ist Katsumatas "parametrische Effektmonade", die durch ein Monoid indiziert wird, wobei die Rendite neutral indiziert wird und die Bindung sich in ihrem Index ansammelt.)

Lassen Sie uns zunächst die Arten überprüfen.

IxMonad (m :: state -> state -> * -> *)

Das heißt, die Art einer "Berechnung" (oder "Aktion", wenn Sie es vorziehen, aber ich bleibe bei "Berechnung") sieht so aus

m before after value

wo before, after :: stateund value :: *. Die Idee ist, die Mittel zu erfassen, um sicher mit einem externen System zu interagieren, das einen vorhersehbaren Zustandsbegriff hat. Der Typ einer Berechnung gibt an, welchen Status beforesie ausführen muss, welchen Status aftersie ausführen soll und (wie bei regulären Monaden *), welche Art von valueBerechnung die Berechnung erzeugt.

Die üblichen Teile sind *wie eine Monade und statewie Domino.

ireturn  ::  a -> m i i a    -- returning a pure value preserves state
ibind    ::  m i j a ->      -- we can go from i to j and get an a, thence
             (a -> m j k b)  -- we can go from j to k and get a b, therefore
             -> m i k b      -- we can indeed go from i to k and get a b

Der so erzeugte Begriff "Kleisli-Pfeil" (Funktion, die Berechnung liefert) ist

a -> m i j b   -- values a in, b out; state transition i to j

und wir bekommen eine Komposition

icomp :: IxMonad m => (b -> m j k c) -> (a -> m i j b) -> a -> m i k c
icomp f g = \ a -> ibind (g a) f

und wie immer stellen die Gesetze genau dies sicher ireturnund icompgeben uns eine Kategorie

      ireturn `icomp` g = g
      f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)

oder, in Comedy Fake C / Java / was auch immer,

      g(); skip = g()
      skip; f() = f()
{g(); h()}; f() = h(); {g(); f()}

Warum die Mühe? Modellierung von "Regeln" der Interaktion. Sie können beispielsweise keine DVD auswerfen, wenn sich keine im Laufwerk befindet, und Sie können keine DVD in das Laufwerk einlegen, wenn sich bereits eine darin befindet. So

data DVDDrive :: Bool -> Bool -> * -> * where  -- Bool is "drive full?"
  DReturn :: a -> DVDDrive i i a
  DInsert :: DVD ->                   -- you have a DVD
             DVDDrive True k a ->     -- you know how to continue full
             DVDDrive False k a       -- so you can insert from empty
  DEject  :: (DVD ->                  -- once you receive a DVD
              DVDDrive False k a) ->  -- you know how to continue empty
             DVDDrive True k a        -- so you can eject when full

instance IxMonad DVDDrive where  -- put these methods where they need to go
  ireturn = DReturn              -- so this goes somewhere else
  ibind (DReturn a)     k  = k a
  ibind (DInsert dvd j) k  = DInsert dvd (ibind j k)
  ibind (DEject j)      k  = DEject j $ \ dvd -> ibind (j dvd) k

Mit dieser Option können wir die "primitiven" Befehle definieren

dInsert :: DVD -> DVDDrive False True ()
dInsert dvd = DInsert dvd $ DReturn ()

dEject :: DVDrive True False DVD
dEject = DEject $ \ dvd -> DReturn dvd

von denen andere mit ireturnund zusammengesetzt werden ibind. Jetzt kann ich schreiben ( doLeihnotation)

discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'

aber nicht das physikalisch Unmögliche

discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dInsert dvd; dEject      -- ouch!

Alternativ kann man seine primitiven Befehle direkt definieren

data DVDCommand :: Bool -> Bool -> * -> * where
  InsertC  :: DVD -> DVDCommand False True ()
  EjectC   :: DVDCommand True False DVD

und instanziieren Sie dann die generische Vorlage

data CommandIxMonad :: (state -> state -> * -> *) ->
                        state -> state -> * -> * where
  CReturn  :: a -> CommandIxMonad c i i a
  (:?)     :: c i j a -> (a -> CommandIxMonad c j k b) ->
                CommandIxMonad c i k b

instance IxMonad (CommandIxMonad c) where
  ireturn = CReturn
  ibind (CReturn a) k  = k a
  ibind (c :? j)    k  = c :? \ a -> ibind (j a) k

Tatsächlich haben wir gesagt, was die primitiven Kleisli-Pfeile sind (was ein "Domino" ist), und dann einen geeigneten Begriff der "Rechensequenz" darüber aufgebaut.

Beachten Sie, dass für jede indizierte Monade mdie "Diagonale ohne Änderung" m i ieine Monade ist, im Allgemeinen m i jjedoch nicht. Darüber hinaus werden Werte nicht indiziert, sondern Berechnungen indiziert, sodass eine indizierte Monade nicht nur die übliche Idee einer für eine andere Kategorie instanziierten Monade ist.

Schauen Sie sich nun noch einmal die Art eines Kleisli-Pfeils an

a -> m i j b

Wir wissen, dass wir in einem Zustand sein müssen, um izu beginnen, und wir sagen voraus, dass jede Fortsetzung von einem Zustand aus beginnen wird j. Wir wissen viel über dieses System! Dies ist keine riskante Operation! Wenn wir die DVD in das Laufwerk legen, geht es rein! Das DVD-Laufwerk kann nach jedem Befehl nicht sagen, wie der Status ist.

Aber das stimmt im Allgemeinen nicht, wenn man mit der Welt interagiert. Manchmal müssen Sie möglicherweise etwas Kontrolle abgeben und die Welt tun lassen, was sie will. Wenn Sie beispielsweise ein Server sind, können Sie Ihrem Client eine Auswahl anbieten, und Ihr Sitzungsstatus hängt von der Auswahl ab. Die Operation "Angebotsauswahl" des Servers bestimmt nicht den resultierenden Status, aber der Server sollte trotzdem weitermachen können. Es ist kein "primitiver Befehl" im obigen Sinne, daher sind indizierte Monaden kein so gutes Werkzeug, um das unvorhersehbare Szenario zu modellieren .

Was ist ein besseres Werkzeug?

type f :-> g = forall state. f state -> g state

class MonadIx (m :: (state -> *) -> (state -> *)) where
  returnIx    :: x :-> m x
  flipBindIx  :: (a :-> m b) -> (m a :-> m b)  -- tidier than bindIx

Gruselige Kekse? Aus zwei Gründen nicht wirklich. Man sieht es eher wie das, was eine Monade ist, denn es ist eine Monade, sondern über (state -> *)statt *. Zweitens, wenn Sie sich die Art eines Kleisli-Pfeils ansehen,

a :-> m b   =   forall state. a state -> m b state

Sie erhalten die Art der Berechnungen mit einer Vor- a und Nachbedingung b, genau wie in Good Old Hoare Logic. Behauptungen in der Programmlogik haben weniger als ein halbes Jahrhundert gebraucht, um die Curry-Howard-Korrespondenz zu durchqueren und zu Haskell-Typen zu werden. Die Art von returnIxsagt "Sie können jede Nachbedingung erreichen, die gilt, indem Sie einfach nichts tun", was die Hoare-Logik-Regel für "Überspringen" ist. Die entsprechende Zusammensetzung ist die Hoare-Logik-Regel für ";".

Lassen Sie uns zum Schluss die Art von betrachten bindIxund alle Quantifizierer eingeben.

bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i

Diese forallhaben entgegengesetzte Polarität. Wir wählen den Anfangszustand iund eine Berechnung, die imit der Nachbedingung beginnen kann a. Die Welt wählt jeden Zwischenzustand, den jsie mag, aber sie muss uns den Beweis liefern, dass die Nachbedingung bgilt, und von jedem solchen Zustand aus können wir bweitermachen. So können wir nacheinander die Bedingung bvom Zustand aus erreichen i. Indem wir die "Nach" -Zustände loslassen, können wir unvorhersehbare Berechnungen modellieren .

Beides IxMonadund MonadIxsind nützlich. Beide Modelle validieren interaktive Berechnungen in Bezug auf sich ändernde Zustände, vorhersehbar bzw. unvorhersehbar. Vorhersehbarkeit ist wertvoll, wenn Sie sie erhalten können, aber Unvorhersehbarkeit ist manchmal eine Tatsache des Lebens. Hoffentlich gibt diese Antwort einen Hinweis darauf, was indizierte Monaden sind, und sagt voraus, wann sie nützlich werden und wann sie aufhören.

Schweinearbeiter
quelle
1
Wie können Sie die True/ False-Werte als Typargumente übergeben DVDDrive? Ist das eine Erweiterung oder geben die Booleschen Werte tatsächlich hier ein?
Bergi
8
@Bergi Die Booleschen Werte wurden "aufgehoben", um auf Typebene zu existieren. Dies ist in Haskell mit der DataKindsErweiterung und in abhängig getippten Sprachen möglich ... nun, das ist so ziemlich das Ganze.
J. Abrahamson
Könnten Sie etwas näher darauf eingehen MonadIx, vielleicht mit Beispielen? Ist es aus theoretischen Gründen besser oder besser für die praktische Anwendung?
Christian Conkle
2
@ChristianConkle Mir ist klar, dass das nicht besonders hilfreich ist. Aber Sie werfen eine ganz andere Frage auf. Wenn ich lokal sage, dass MonadIx "besser" ist, meine ich dies im Zusammenhang mit der Modellierung von Interaktionen mit einer unvorhersehbaren Umgebung. Wenn Ihr DVD-Laufwerk DVDs ausspucken darf, gefällt es Ihnen nicht, wenn Sie versuchen, sie einzulegen. Einige praktische Situationen verhalten sich so schlecht. Andere sind vorhersehbarer (dh Sie können sagen, in welchem ​​Zustand eine Fortsetzung beginnt und nicht, dass Vorgänge nicht fehlschlagen). In diesem Fall ist es einfacher, mit IxMonad zu arbeiten.
Schweinearbeiter
1
Wenn Sie die Notation not in der Antwort "ausleihen", kann es nützlich sein zu sagen, dass es sich tatsächlich um eine gültige Syntax mit der RebindableSyntaxErweiterung handelt. Eine Erwähnung anderer erforderlicher Erweiterungen wäre nett, wie die oben genanntenDataKinds
Gigabyte
46

Es gibt mindestens drei Möglichkeiten, eine indizierte Monade zu definieren, die ich kenne.

Ich werde diese Optionen als indizierte Monaden à la X bezeichnen , wobei X über den Informatikern Bob Atkey, Conor McBride und Dominic Orchard liegt, da ich sie so sehe. Teile dieser Konstruktionen haben eine viel längere, illustrere Geschichte und schönere Interpretationen durch die Kategorietheorie, aber ich habe zuerst von ihnen erfahren, die mit diesen Namen verbunden sind, und ich versuche zu verhindern, dass diese Antwort zu esoterisch wird.

Atkey

Bob Atkeys Stil der indizierten Monade besteht darin, mit zwei zusätzlichen Parametern zu arbeiten, um den Index der Monade zu behandeln.

Damit erhalten Sie die Definitionen, die die Leute in anderen Antworten herumgeworfen haben:

class IMonad m where
  ireturn  ::  a -> m i i a
  ibind    ::  m i j a -> (a -> m j k b) -> m i k b

Wir können auch indizierte Comonaden à la Atkey definieren. Ich bekomme tatsächlich eine Menge Kilometer aus denen in der lensCodebasis .

McBride

Die nächste Form der indizierten Monade ist Conor McBrides Definition aus seiner Arbeit "Kleisli Arrows of Outrageous Fortune" . Er verwendet stattdessen einen einzelnen Parameter für den Index. Dadurch hat die indizierte Monadendefinition eine ziemlich clevere Form.

Wenn wir eine natürliche Transformation unter Verwendung der Parametrizität wie folgt definieren

type a ~> b = forall i. a i -> b i 

dann können wir McBrides Definition als aufschreiben

class IMonad m where
  ireturn :: a ~> m a
  ibind :: (a ~> m b) -> (m a ~> m b)

Das fühlt sich ganz anders an als bei Atkey, aber es fühlt sich eher wie eine normale Monade an, anstatt eine Monade aufzubauen (m :: * -> *) aufzubauen, bauen wir darauf auf (m :: (k -> *) -> (k -> *).

Interessanterweise können Sie Atkeys Stil der indizierten Monade tatsächlich von McBrides wiederherstellen, indem Sie einen cleveren Datentyp verwenden, den McBride in seinem unnachahmlichen Stil als "at key" bezeichnet.

data (:=) :: a i j where
   V :: a -> (a := i) i

Jetzt können Sie das herausfinden

ireturn :: IMonad m => (a := j) ~> m (a := j)

das erweitert sich zu

ireturn :: IMonad m => (a := j) i -> m (a := j) i

kann nur aufgerufen werden, wenn j = i ist, und dann kann ein sorgfältiges Lesen von ibindSie das gleiche wie bei Atkey zurückbringen ibind. Sie müssen diese (: =) Datenstrukturen weitergeben, aber sie stellen die Leistung der Atkey-Präsentation wieder her.

Andererseits ist die Atkey-Präsentation nicht stark genug, um alle Verwendungen der McBride-Version wiederherzustellen. Macht wurde streng gewonnen.

Eine andere schöne Sache ist, dass McBrides indizierte Monade eindeutig eine Monade ist, es ist nur eine Monade in einer anderen Funktorkategorie. Es funktioniert über Endofunktoren in der Kategorie der Funktoren von (k -> *)bis (k -> *)und nicht in der Kategorie der Funktoren von *bis *.

Eine unterhaltsame Übung besteht darin, herauszufinden, wie die Konvertierung von McBride in Atkey für indizierte Comonaden durchgeführt wird . Ich persönlich verwende einen Datentyp 'At' für die "at key" -Konstruktion in McBrides Artikel. Ich ging auf der ICFP 2013 tatsächlich zu Bob Atkey und erwähnte, dass ich ihn auf den Kopf gestellt hatte, um ihn zu einem "Mantel" zu machen. Er schien sichtlich verstört zu sein. Die Linie spielte sich in meinem Kopf besser ab. =)

Obstgarten

Schließlich ist ein dritter, weit weniger häufig genannter Antragsteller auf den Namen "indizierte Monade" Dominic Orchard zu verdanken, wo er stattdessen ein Monoid auf Typebene verwendet, um Indizes zusammenzuschlagen. Anstatt die Details der Konstruktion durchzugehen, werde ich einfach auf diesen Vortrag verlinken:

https://github.com/dorchard/effect-monad/blob/master/docs/ixmonad-fita14.pdf

Edward KMETT
quelle
1
Habe ich Recht, dass die Monade von Orchard der von Atkey entspricht, da wir von der ersteren zur letzteren wechseln können, indem wir das Endomorphismus-Monoid nehmen und durch CPS-Codierung monoidaler Anhänge im Zustandsübergang rückwärts gehen?
András Kovács
Das klingt für mich plausibel.
Edward KMETT
Basierend auf etwas, das er mir auf der ICFP 2013 gesagt hat, glaube ich, dass Orchard beabsichtigte, dass seine Typfamilien sich eher wie ein echtes Monoid verhalten als wie eine willkürliche Kategorie, in der einige der Pfeile keine Verbindung herstellen können, sodass die Geschichte möglicherweise mehr enthält Darüber hinaus können Sie mit Atkeys Konstruktion einige Kleisli-Aktionen leicht daran hindern, sich mit anderen zu verbinden - in vielerlei Hinsicht ist dies der eigentliche Punkt und die McBride-Version.
Edward KMETT
2
So erweitern Sie das "sorgfältige Lesen von ibind": Geben Sie den Typalias ein Atkey m i j a = m (a := j) i. Wenn Sie dies als mDefinition in Atkey verwenden, werden die beiden Signaturen wiederhergestellt, nach denen wir suchen: ireturnAtkin :: a -> m (a := i) iund ibindAtkin :: m (a := j) i -> (a -> m (b := k) j) -> m (b := k) i. Der erste wird durch Zusammensetzung erhalten : ireturn . V. Die zweite durch (1) Erstellen einer Funktion forall j. (a := j) j -> m (b := k) jdurch Mustervergleich und Übergeben der wiederhergestellten Funktion aan das zweite Argument von ibindAtkin.
WorldSEnder
23

Nehmen Sie als einfaches Szenario an, Sie haben eine staatliche Monade. Der Zustandstyp ist komplex und groß, aber alle diese Zustände können in zwei Gruppen unterteilt werden: rote und blaue Zustände. Einige Operationen in dieser Monade sind nur dann sinnvoll, wenn der aktuelle Status ein blauer Status ist. Unter diesen behalten einige den Status blau ( blueToBlue), während andere den Status rot ( blueToRed) halten. In einer regulären Monade konnten wir schreiben

blueToRed  :: State S ()
blueToBlue :: State S ()

foo :: State S ()
foo = do blueToRed
         blueToBlue

Auslösen eines Laufzeitfehlers, da die zweite Aktion einen blauen Status erwartet. Wir möchten dies statisch verhindern. Indizierte Monade erfüllt dieses Ziel:

data Red
data Blue

-- assume a new indexed State monad
blueToRed  :: State S Blue Red  ()
blueToBlue :: State S Blue Blue ()

foo :: State S ?? ?? ()
foo = blueToRed `ibind` \_ ->
      blueToBlue          -- type error

Ein Typfehler wird ausgelöst, weil sich der zweite Index von blueToRed( Red) vom ersten Index von blueToBlue( Blue) unterscheidet.

Als weiteres Beispiel können Sie mit indizierten Monaden einer Statusmonade erlauben, den Typ für ihren Status zu ändern, z

data State old new a = State (old -> (new, a))

Sie können das oben Gesagte verwenden, um einen Status zu erstellen, bei dem es sich um einen statisch typisierten heterogenen Stapel handelt. Operationen hätten Typ

push :: a -> State old (a,old) ()
pop  :: State (a,new) new a

Angenommen, Sie möchten eine eingeschränkte E / A-Monade, die keinen Dateizugriff zulässt. Sie könnten zB verwenden

openFile :: IO any FilesAccessed ()
newIORef :: a -> IO any any (IORef a)
-- no operation of type :: IO any NoAccess _

Auf diese Weise IO ... NoAccess ()wird statisch garantiert, dass eine Aktion mit Typ frei von Dateizugriff ist. Stattdessen kann eine Aktion vom Typ IO ... FilesAccessed ()auf Dateien zugreifen. Eine indizierte Monade würde bedeuten, dass Sie keinen separaten Typ für das eingeschränkte E / A erstellen müssen, wodurch jede nicht dateibezogene Funktion in beiden E / A-Typen dupliziert werden müsste.

Chi
quelle
18

Eine indizierte Monade ist keine bestimmte Monade wie beispielsweise die staatliche Monade, sondern eine Art Verallgemeinerung des Monadenkonzepts mit zusätzlichen Typparametern.

Während ein "Standard" -Monadenwert den Typ hat, den Monad m => m aein Wert in einer indizierten Monade wäreIndexedMonad m => m i j a wo iund jsind Indextypen, so dass dies ider Typ des Index zu Beginn der monadischen Berechnung und jam Ende der Berechnung ist. In gewisser Weise können Sie sich ieine Art Eingabetyp und einen jAusgabetyp vorstellen.

Verwenden von State Beispiel State s abehält eine statusbehaftete Berechnung swährend der gesamten Berechnung einen Typstatus bei und gibt ein Ergebnis vom Typ zurück a. Eine indizierte Version IndexedState i j aist eine zustandsbehaftete Berechnung, bei der der Zustand während der Berechnung in einen anderen Typ geändert werden kann. Der Anfangszustand hat den Typ iund den Zustand und das Ende der Berechnung hat den Typj .

Die Verwendung einer indizierten Monade gegenüber einer normalen Monade ist selten erforderlich, kann jedoch in einigen Fällen zur Codierung strengerer statischer Garantien verwendet werden.

shang
quelle
5

Es kann wichtig sein, einen Blick darauf zu werfen, wie die Indizierung in abhängigen Typen (z. B. in Agda) verwendet wird. Dies kann erklären, wie die Indizierung im Allgemeinen hilft, und diese Erfahrung dann in Monaden übersetzen.

Durch die Indizierung können Beziehungen zwischen bestimmten Instanzen von Typen hergestellt werden. Dann können Sie über einige Werte nachdenken, um festzustellen, ob diese Beziehung gilt.

Zum Beispiel (in agda) können Sie angeben, dass einige natürliche Zahlen verwandt sind _<_, und der Typ gibt an, um welche Zahlen es sich handelt. Dann können Sie verlangen, dass eine Funktion einen Zeugen erhält m < n, der nur dann ordnungsgemäß funktioniert - und ohne diesen Zeugen wird das Programm nicht kompiliert.

Als weiteres Beispiel könnten Sie bei ausreichender Ausdauer und Compiler-Unterstützung für die von Ihnen gewählte Sprache codieren, dass die Funktion davon ausgeht, dass eine bestimmte Liste sortiert ist.

Indizierte Monaden ermöglichen es, einige der Funktionen abhängiger Systeme zu codieren, um Nebenwirkungen genauer zu verwalten.

Sassa NF
quelle