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?