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 :: state
und 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 before
sie ausführen muss, welchen Status after
sie ausführen soll und (wie bei regulären Monaden *
), welche Art von value
Berechnung die Berechnung erzeugt.
Die üblichen Teile sind *
wie eine Monade und state
wie 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 ireturn
und icomp
geben 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 ireturn
und zusammengesetzt werden ibind
. Jetzt kann ich schreiben ( do
Leihnotation)
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 m
die "Diagonale ohne Änderung" m i i
eine Monade ist, im Allgemeinen m i j
jedoch 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 i
zu 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 returnIx
sagt "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 bindIx
und alle Quantifizierer eingeben.
bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i
Diese forall
haben entgegengesetzte Polarität. Wir wählen den Anfangszustand i
und eine Berechnung, die i
mit der Nachbedingung beginnen kann a
. Die Welt wählt jeden Zwischenzustand, den j
sie mag, aber sie muss uns den Beweis liefern, dass die Nachbedingung b
gilt, und von jedem solchen Zustand aus können wir b
weitermachen. So können wir nacheinander die Bedingung b
vom Zustand aus erreichen i
. Indem wir die "Nach" -Zustände loslassen, können wir unvorhersehbare Berechnungen modellieren .
Beides IxMonad
und MonadIx
sind 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.
True
/False
-Werte als Typargumente übergebenDVDDrive
? Ist das eine Erweiterung oder geben die Booleschen Werte tatsächlich hier ein?DataKinds
Erweiterung und in abhängig getippten Sprachen möglich ... nun, das ist so ziemlich das Ganze.MonadIx
, vielleicht mit Beispielen? Ist es aus theoretischen Gründen besser oder besser für die praktische Anwendung?RebindableSyntax
Erweiterung handelt. Eine Erwähnung anderer erforderlicher Erweiterungen wäre nett, wie die oben genanntenDataKinds
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:
Wir können auch indizierte Comonaden à la Atkey definieren. Ich bekomme tatsächlich eine Menge Kilometer aus denen in der
lens
Codebasis .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
dann können wir McBrides Definition als aufschreiben
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.
Jetzt können Sie das herausfinden
das erweitert sich zu
kann nur aufgerufen werden, wenn j = i ist, und dann kann ein sorgfältiges Lesen von
ibind
Sie das gleiche wie bei Atkey zurückbringenibind
. 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
quelle
ibind
": Geben Sie den Typalias einAtkey m i j a = m (a := j) i
. Wenn Sie dies alsm
Definition in Atkey verwenden, werden die beiden Signaturen wiederhergestellt, nach denen wir suchen:ireturnAtkin :: a -> m (a := i) i
undibindAtkin :: 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 Funktionforall j. (a := j) j -> m (b := k) j
durch Mustervergleich und Übergeben der wiederhergestellten Funktiona
an das zweite Argument vonibindAtkin
.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 schreibenAuslösen eines Laufzeitfehlers, da die zweite Aktion einen blauen Status erwartet. Wir möchten dies statisch verhindern. Indizierte Monade erfüllt dieses Ziel:
Ein Typfehler wird ausgelöst, weil sich der zweite Index von
blueToRed
(Red
) vom ersten Index vonblueToBlue
(Blue
) unterscheidet.Als weiteres Beispiel können Sie mit indizierten Monaden einer Statusmonade erlauben, den Typ für ihren Status zu ändern, z
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
Angenommen, Sie möchten eine eingeschränkte E / A-Monade, die keinen Dateizugriff zulässt. Sie könnten zB verwenden
Auf diese Weise
IO ... NoAccess ()
wird statisch garantiert, dass eine Aktion mit Typ frei von Dateizugriff ist. Stattdessen kann eine Aktion vom TypIO ... 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.quelle
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 a
ein Wert in einer indizierten Monade wäreIndexedMonad m => m i j a
woi
undj
sind Indextypen, so dass diesi
der Typ des Index zu Beginn der monadischen Berechnung undj
am Ende der Berechnung ist. In gewisser Weise können Sie sichi
eine Art Eingabetyp und einenj
Ausgabetyp vorstellen.Verwenden von
State
BeispielState s a
behält eine statusbehaftete Berechnungs
während der gesamten Berechnung einen Typstatus bei und gibt ein Ergebnis vom Typ zurücka
. Eine indizierte VersionIndexedState i j a
ist eine zustandsbehaftete Berechnung, bei der der Zustand während der Berechnung in einen anderen Typ geändert werden kann. Der Anfangszustand hat den Typi
und 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.
quelle
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ältm < 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.
quelle