Wie der Kinderfänger in Chitty-Chitty-Bang-Bang, der Kinder mit Süßigkeiten und Spielzeug in die Gefangenschaft lockt, täuschen Rekrutierer für ein Physikstudium gerne mit Seifenblasen und Bumerangs herum, aber wenn die Tür zuklappt, ist es "Richtig, Kinder, Zeit zu lernen über partielle Differenzierung! ". Ich auch. Sag nicht, ich hätte dich nicht gewarnt.
Hier ist eine weitere Warnung: Der folgende Code benötigt {-# LANGUAGE KitchenSink #-}
oder besser gesagt
{-# LANGUAGE TypeFamilies, FlexibleContexts, TupleSections, GADTs, DataKinds,
TypeOperators, FlexibleInstances, RankNTypes, ScopedTypeVariables,
StandaloneDeriving, UndecidableInstances #-}
In keiner bestimmten Reihenfolge.
Differenzierbare Funktoren geben komonadische Reißverschlüsse
Was ist überhaupt ein differenzierbarer Funktor?
class (Functor f, Functor (DF f)) => Diff1 f where
type DF f :: * -> *
upF :: ZF f x -> f x
downF :: f x -> f (ZF f x)
aroundF :: ZF f x -> ZF f (ZF f x)
data ZF f x = (:<-:) {cxF :: DF f x, elF :: x}
Es ist ein Funktor, der eine Ableitung hat, die auch ein Funktor ist. Die Ableitung repräsentiert einen Ein-Loch-Kontext für ein Element . Der Reißverschlusstyp ZF f x
repräsentiert das Paar aus einem Ein-Loch-Kontext und dem Element im Loch.
Die Operationen zur Diff1
Beschreibung der Navigationsarten, die wir mit Reißverschlüssen ausführen können (ohne die Vorstellung von "nach links" und "nach rechts", siehe mein Papier " Clowns und Joker" ). Wir können "nach oben" gehen und die Struktur wieder zusammensetzen, indem wir das Element in sein Loch stecken. Wir können "nach unten" gehen und jeden Weg finden, um ein Element in einer bestimmten Struktur zu besuchen: Wir dekorieren jedes Element mit seinem Kontext. Wir können "herumgehen", einen vorhandenen Reißverschluss nehmen und jedes Element mit seinem Kontext dekorieren, sodass wir alle Möglichkeiten finden, uns neu zu konzentrieren (und unseren aktuellen Fokus beizubehalten).
Nun, die Art von aroundF
könnte einige von Ihnen daran erinnern
class Functor c => Comonad c where
extract :: c x -> x
duplicate :: c x -> c (c x)
und du hast Recht, daran erinnert zu werden! Wir haben mit einem Sprung und einem Sprung,
instance Diff1 f => Functor (ZF f) where
fmap f (df :<-: x) = fmap f df :<-: f x
instance Diff1 f => Comonad (ZF f) where
extract = elF
duplicate = aroundF
und wir bestehen darauf
extract . duplicate == id
fmap extract . duplicate == id
duplicate . duplicate == fmap duplicate . duplicate
Das brauchen wir auch
fmap extract (downF xs) == xs
fmap upF (downF xs) = fmap (const xs) xs
Polynomfunktoren sind differenzierbar
Konstante Funktoren sind differenzierbar.
data KF a x = KF a
instance Functor (KF a) where
fmap f (KF a) = KF a
instance Diff1 (KF a) where
type DF (KF a) = KF Void
upF (KF w :<-: _) = absurd w
downF (KF a) = KF a
aroundF (KF w :<-: _) = absurd w
Es gibt keinen Ort, an dem ein Element platziert werden kann, daher ist es unmöglich, einen Kontext zu bilden. Es gibt nirgendwo zu gehen upF
oder downF
aus, und wir einfach alle keine der Wege finden , um zu gehen downF
.
Der Identitätsfunktor ist differenzierbar.
data IF x = IF x
instance Functor IF where
fmap f (IF x) = IF (f x)
instance Diff1 IF where
type DF IF = KF ()
upF (KF () :<-: x) = IF x
downF (IF x) = IF (KF () :<-: x)
aroundF z@(KF () :<-: x) = KF () :<-: z
Es gibt ein Element in einem trivialen Kontext, downF
findet es, upF
packt es neu und aroundF
kann nur stehen bleiben.
Die Summe bewahrt die Differenzierbarkeit.
data (f :+: g) x = LF (f x) | RF (g x)
instance (Functor f, Functor g) => Functor (f :+: g) where
fmap h (LF f) = LF (fmap h f)
fmap h (RF g) = RF (fmap h g)
instance (Diff1 f, Diff1 g) => Diff1 (f :+: g) where
type DF (f :+: g) = DF f :+: DF g
upF (LF f' :<-: x) = LF (upF (f' :<-: x))
upF (RF g' :<-: x) = RF (upF (g' :<-: x))
Die anderen Teile sind eher eine Handvoll. Um zu gehen downF
, müssen wir downF
in die markierte Komponente gehen und dann die resultierenden Reißverschlüsse reparieren, um das Tag im Kontext anzuzeigen.
downF (LF f) = LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) (downF f))
downF (RF g) = RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) (downF g))
Zum aroundF
Entfernen entfernen wir das Etikett, finden heraus, wie Sie das nicht markierte Objekt umgehen können, und stellen dann das Etikett in allen resultierenden Reißverschlüssen wieder her. Das fokussierte Element x
wird durch seinen gesamten Reißverschluss ersetzt z
.
aroundF z@(LF f' :<-: (x :: x)) =
LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) . cxF $ aroundF (f' :<-: x :: ZF f x))
:<-: z
aroundF z@(RF g' :<-: (x :: x)) =
RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) . cxF $ aroundF (g' :<-: x :: ZF g x))
:<-: z
Beachten Sie, dass ich verwenden musste, um ScopedTypeVariables
die rekursiven Aufrufe von zu eindeutig zu machen aroundF
. Als Typ Funktion DF
ist nicht injektiv, so dass die Tatsache, dass f' :: D f x
nicht ausreicht, um zu erzwingen f' :<-: x :: Z f x
.
Das Produkt bewahrt die Differenzierbarkeit.
data (f :*: g) x = f x :*: g x
instance (Functor f, Functor g) => Functor (f :*: g) where
fmap h (f :*: g) = fmap h f :*: fmap h g
Um sich auf ein Element in einem Paar zu konzentrieren, konzentrieren Sie sich entweder auf die linke Seite und lassen die rechte Seite in Ruhe oder umgekehrt. Leibniz 'berühmte Produktregel entspricht einer einfachen räumlichen Intuition!
instance (Diff1 f, Diff1 g) => Diff1 (f :*: g) where
type DF (f :*: g) = (DF f :*: g) :+: (f :*: DF g)
upF (LF (f' :*: g) :<-: x) = upF (f' :<-: x) :*: g
upF (RF (f :*: g') :<-: x) = f :*: upF (g' :<-: x)
Funktioniert jetzt downF
ähnlich wie bei Summen, außer dass wir den Reißverschlusskontext nicht nur mit einem Tag (um zu zeigen, welchen Weg wir gegangen sind), sondern auch mit der unberührten anderen Komponente korrigieren müssen.
downF (f :*: g)
= fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f)
:*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g)
Aber aroundF
ist eine riesige Tasche voller Lacher. Welche Seite wir gerade besuchen, wir haben zwei Möglichkeiten:
- Bewegen Sie sich
aroundF
auf dieser Seite.
- Bewegen Sie
upF
sich von dieser Seite downF
auf die andere Seite.
In jedem Fall müssen wir die Operationen für die Unterstruktur verwenden und dann die Kontexte reparieren.
aroundF z@(LF (f' :*: g) :<-: (x :: x)) =
LF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x)
(cxF $ aroundF (f' :<-: x :: ZF f x))
:*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g))
:<-: z
where f = upF (f' :<-: x)
aroundF z@(RF (f :*: g') :<-: (x :: x)) =
RF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f) :*:
fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x)
(cxF $ aroundF (g' :<-: x :: ZF g x)))
:<-: z
where g = upF (g' :<-: x)
Puh! Die Polynome sind alle differenzierbar und geben uns daher Comonaden.
Hmm. Es ist alles ein bisschen abstrakt. Also fügte ich hinzu, deriving Show
wo immer ich konnte, und warf mich hinein
deriving instance (Show (DF f x), Show x) => Show (ZF f x)
was die folgende Interaktion ermöglichte (von Hand aufgeräumt)
> downF (IF 1 :*: IF 2)
IF (LF (KF () :*: IF 2) :<-: 1) :*: IF (RF (IF 1 :*: KF ()) :<-: 2)
> fmap aroundF it
IF (LF (KF () :*: IF (RF (IF 1 :*: KF ()) :<-: 2)) :<-: (LF (KF () :*: IF 2) :<-: 1))
:*:
IF (RF (IF (LF (KF () :*: IF 2) :<-: 1) :*: KF ()) :<-: (RF (IF 1 :*: KF ()) :<-: 2))
Übung Zeigen Sie anhand der Kettenregel, dass die Zusammensetzung differenzierbarer Funktoren differenzierbar ist .
Süss! Können wir jetzt nach Hause gehen? Natürlich nicht. Wir haben noch keine rekursiven Strukturen unterschieden.
Rekursive Funktoren aus Bifunktoren herstellen
A ist Bifunctor
, wie die vorhandene Literatur zur generischen Programmierung von Datentypen (siehe Arbeiten von Patrik Jansson und Johan Jeuring oder ausgezeichnete Vorlesungsunterlagen von Jeremy Gibbons) ausführlich erklärt, ein Typkonstruktor mit zwei Parametern, die zwei Arten von Unterstrukturen entsprechen. Wir sollten in der Lage sein, beide "abzubilden".
class Bifunctor b where
bimap :: (x -> x') -> (y -> y') -> b x y -> b x' y'
Wir können Bifunctor
s verwenden, um die Knotenstruktur rekursiver Container anzugeben. Jeder Knoten hat Unterknoten und Elemente . Dies können nur die zwei Arten von Unterstrukturen sein.
data Mu b y = In (b (Mu b y) y)
Sehen? Wir "binden den rekursiven Knoten" im b
ersten Argument und behalten den Parameter y
im zweiten. Dementsprechend erhalten wir ein für allemal
instance Bifunctor b => Functor (Mu b) where
fmap f (In b) = In (bimap (fmap f) f b)
Um dies zu nutzen, benötigen wir eine Reihe von Bifunctor
Instanzen.
Das Bifunctor Kit
Konstanten sind bifunktoriell.
newtype K a x y = K a
instance Bifunctor (K a) where
bimap f g (K a) = K a
Sie können sagen, dass ich dieses Bit zuerst geschrieben habe, weil die Bezeichner kürzer sind, aber das ist gut, weil der Code länger ist.
Variablen sind bifunktoriell.
Wir brauchen die Bifunktoren, die dem einen oder anderen Parameter entsprechen, also habe ich einen Datentyp erstellt, um sie zu unterscheiden, und dann ein geeignetes GADT definiert.
data Var = X | Y
data V :: Var -> * -> * -> * where
XX :: x -> V X x y
YY :: y -> V Y x y
Das macht V X x y
eine Kopie von x
und V Y x y
eine Kopie von y
. Entsprechend
instance Bifunctor (V v) where
bimap f g (XX x) = XX (f x)
bimap f g (YY y) = YY (g y)
Summen und Produkte von Bifunktoren sind Bifunktoren
data (:++:) f g x y = L (f x y) | R (g x y) deriving Show
instance (Bifunctor b, Bifunctor c) => Bifunctor (b :++: c) where
bimap f g (L b) = L (bimap f g b)
bimap f g (R b) = R (bimap f g b)
data (:**:) f g x y = f x y :**: g x y deriving Show
instance (Bifunctor b, Bifunctor c) => Bifunctor (b :**: c) where
bimap f g (b :**: c) = bimap f g b :**: bimap f g c
So weit, so Boilerplate, aber jetzt können wir Dinge wie definieren
List = Mu (K () :++: (V Y :**: V X))
Bin = Mu (V Y :**: (K () :++: (V X :**: V X)))
Wenn Sie diese Typen für tatsächliche Daten verwenden und nicht in der pointillistischen Tradition von Georges Seurat erblinden möchten, verwenden Sie Mustersynonyme .
Aber was ist mit Reißverschlüssen? Wie sollen wir zeigen, dass Mu b
das differenzierbar ist? Wir müssen zeigen, dass b
dies in beiden Variablen differenzierbar ist. Klirren! Es ist Zeit, etwas über partielle Differenzierung zu lernen.
Partielle Derivate von Bifunktoren
Da wir zwei Variablen haben, müssen wir in der Lage sein, manchmal und einzeln zu anderen Zeiten gemeinsam darüber zu sprechen. Wir werden die Singleton-Familie brauchen:
data Vary :: Var -> * where
VX :: Vary X
VY :: Vary Y
Jetzt können wir sagen, was es für einen Bifunctor bedeutet, an jeder Variablen partielle Ableitungen zu haben, und den entsprechenden Begriff des Reißverschlusses angeben.
class (Bifunctor b, Bifunctor (D b X), Bifunctor (D b Y)) => Diff2 b where
type D b (v :: Var) :: * -> * -> *
up :: Vary v -> Z b v x y -> b x y
down :: b x y -> b (Z b X x y) (Z b Y x y)
around :: Vary v -> Z b v x y -> Z b v (Z b X x y) (Z b Y x y)
data Z b v x y = (:<-) {cxZ :: D b v x y, elZ :: V v x y}
Diese D
Operation muss wissen, auf welche Variable abgezielt werden soll. Der entsprechende Reißverschluss gibt an Z b v
, welche Variable v
im Fokus stehen muss. Wenn wir "mit Kontext dekorieren", müssen wir x
-elements mit X
-contexts und y
-elements mit Y
-contexts dekorieren. Aber sonst ist es die gleiche Geschichte.
Wir haben noch zwei Aufgaben: Erstens, um zu zeigen, dass unser Bifunctor-Kit differenzierbar ist; zweitens, um zu zeigen, Diff2 b
dass wir uns etablieren können Diff1 (Mu b)
.
Differenzierung des Bifunctor-Kits
Ich fürchte, dieses Stück ist eher fummelig als erbaulich. Fühlen Sie sich frei, um zu überspringen.
Die Konstanten sind wie zuvor.
instance Diff2 (K a) where
type D (K a) v = K Void
up _ (K q :<- _) = absurd q
down (K a) = K a
around _ (K q :<- _) = absurd q
Bei dieser Gelegenheit ist das Leben zu kurz, um die Theorie des Kronecker-Deltas auf Typebene zu entwickeln, deshalb habe ich die Variablen nur separat behandelt.
instance Diff2 (V X) where
type D (V X) X = K ()
type D (V X) Y = K Void
up VX (K () :<- XX x) = XX x
up VY (K q :<- _) = absurd q
down (XX x) = XX (K () :<- XX x)
around VX z@(K () :<- XX x) = K () :<- XX z
around VY (K q :<- _) = absurd q
instance Diff2 (V Y) where
type D (V Y) X = K Void
type D (V Y) Y = K ()
up VX (K q :<- _) = absurd q
up VY (K () :<- YY y) = YY y
down (YY y) = YY (K () :<- YY y)
around VX (K q :<- _) = absurd q
around VY z@(K () :<- YY y) = K () :<- YY z
Für die strukturellen Fälle fand ich es nützlich, einen Helfer einzuführen, mit dem ich Variablen einheitlich behandeln kann.
vV :: Vary v -> Z b v x y -> V v (Z b X x y) (Z b Y x y)
vV VX z = XX z
vV VY z = YY z
Ich habe dann Gadgets gebaut, um die Art von "Retagging" zu ermöglichen, die wir für down
und benötigen around
. (Natürlich habe ich gesehen, welche Geräte ich während meiner Arbeit brauchte.)
zimap :: (Bifunctor c) => (forall v. Vary v -> D b v x y -> D b' v x y) ->
c (Z b X x y) (Z b Y x y) -> c (Z b' X x y) (Z b' Y x y)
zimap f = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
dzimap :: (Bifunctor (D c X), Bifunctor (D c Y)) =>
(forall v. Vary v -> D b v x y -> D b' v x y) ->
Vary v -> Z c v (Z b X x y) (Z b Y x y) -> D c v (Z b' X x y) (Z b' Y x y)
dzimap f VX (d :<- _) = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
d
dzimap f VY (d :<- _) = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
d
Und wenn das Los fertig ist, können wir die Details herausarbeiten. Summen sind einfach.
instance (Diff2 b, Diff2 c) => Diff2 (b :++: c) where
type D (b :++: c) v = D b v :++: D c v
up v (L b' :<- vv) = L (up v (b' :<- vv))
down (L b) = L (zimap (const L) (down b))
down (R c) = R (zimap (const R) (down c))
around v z@(L b' :<- vv :: Z (b :++: c) v x y)
= L (dzimap (const L) v ba) :<- vV v z
where ba = around v (b' :<- vv :: Z b v x y)
around v z@(R c' :<- vv :: Z (b :++: c) v x y)
= R (dzimap (const R) v ca) :<- vV v z
where ca = around v (c' :<- vv :: Z c v x y)
Produkte sind harte Arbeit, deshalb bin ich eher Mathematiker als Ingenieur.
instance (Diff2 b, Diff2 c) => Diff2 (b :**: c) where
type D (b :**: c) v = (D b v :**: c) :++: (b :**: D c v)
up v (L (b' :**: c) :<- vv) = up v (b' :<- vv) :**: c
up v (R (b :**: c') :<- vv) = b :**: up v (c' :<- vv)
down (b :**: c) =
zimap (const (L . (:**: c))) (down b) :**: zimap (const (R . (b :**:))) (down c)
around v z@(L (b' :**: c) :<- vv :: Z (b :**: c) v x y)
= L (dzimap (const (L . (:**: c))) v ba :**:
zimap (const (R . (b :**:))) (down c))
:<- vV v z where
b = up v (b' :<- vv :: Z b v x y)
ba = around v (b' :<- vv :: Z b v x y)
around v z@(R (b :**: c') :<- vv :: Z (b :**: c) v x y)
= R (zimap (const (L . (:**: c))) (down b):**:
dzimap (const (R . (b :**:))) v ca)
:<- vV v z where
c = up v (c' :<- vv :: Z c v x y)
ca = around v (c' :<- vv :: Z c v x y)
Konzeptionell ist es wie zuvor, aber mit mehr Bürokratie. Ich habe diese mithilfe der Pre-Type-Hole-Technologie erstellt, undefined
an Stellen, an denen ich nicht bereit war zu arbeiten, als Stub verwendet und an der Stelle (zu einem bestimmten Zeitpunkt), an der ich einen nützlichen Hinweis vom Typechecker erhalten wollte, einen absichtlichen Tippfehler eingeführt . Auch in Haskell können Sie die Typprüfung als Videospiel-Erfahrung durchführen.
Subknoten-Reißverschlüsse für rekursive Container
Die partielle Ableitung von b
in Bezug auf X
sagt uns, wie man einen Unterknoten einen Schritt innerhalb eines Knotens findet, so dass wir den herkömmlichen Begriff des Reißverschlusses erhalten.
data MuZpr b y = MuZpr
{ aboveMu :: [D b X (Mu b y) y]
, hereMu :: Mu b y
}
Wir können durch wiederholtes Einstecken von X
Positionen bis zur Wurzel zoomen .
muUp :: Diff2 b => MuZpr b y -> Mu b y
muUp (MuZpr {aboveMu = [], hereMu = t}) = t
muUp (MuZpr {aboveMu = (dX : dXs), hereMu = t}) =
muUp (MuZpr {aboveMu = dXs, hereMu = In (up VX (dX :<- XX t))})
Aber wir brauchen Element -zippers.
Element-Reißverschlüsse für Fixpunkte von Bifunktoren
Jedes Element befindet sich irgendwo innerhalb eines Knotens. Dieser Knoten sitzt unter einem Stapel von X
Derivaten. Die Position des Elements in diesem Knoten wird jedoch durch ein Y
Derivat angegeben. Wir bekommen
data MuCx b y = MuCx
{ aboveY :: [D b X (Mu b y) y]
, belowY :: D b Y (Mu b y) y
}
instance Diff2 b => Functor (MuCx b) where
fmap f (MuCx { aboveY = dXs, belowY = dY }) = MuCx
{ aboveY = map (bimap (fmap f) f) dXs
, belowY = bimap (fmap f) f dY
}
Mutig, behaupte ich
instance Diff2 b => Diff1 (Mu b) where
type DF (Mu b) = MuCx b
Aber bevor ich die Operationen entwickle, brauche ich ein paar Kleinigkeiten.
Ich kann Daten zwischen Funktorreißverschlüssen und Bifunktorreißverschlüssen wie folgt tauschen:
zAboveY :: ZF (Mu b) y -> [D b X (Mu b y) y]
zAboveY (d :<-: y) = aboveY d
zZipY :: ZF (Mu b) y -> Z b Y (Mu b y) y
zZipY (d :<-: y) = belowY d :<- YY y
Das reicht aus, um mich definieren zu lassen:
upF z = muUp (MuZpr {aboveMu = zAboveY z, hereMu = In (up VY (zZipY z))})
Das heißt, wir gehen nach oben, indem wir zuerst den Knoten wieder zusammensetzen, an dem sich das Element befindet, einen Elementreißverschluss in einen Unterknotenreißverschluss verwandeln und dann wie oben ganz herauszoomen.
Als nächstes sage ich
downF = yOnDown []
So beginnen Sie mit dem leeren Stapel und definieren die Hilfsfunktion, die down
wiederholt von unterhalb eines Stapels ausgeführt wird:
yOnDown :: Diff2 b => [D b X (Mu b y) y] -> Mu b y -> Mu b (ZF (Mu b) y)
yOnDown dXs (In b) = In (contextualize dXs (down b))
Jetzt down b
bringt uns nur noch in den Knoten. Die Reißverschlüsse, die wir benötigen, müssen auch den Kontext des Knotens tragen. Das contextualise
macht:
contextualize :: (Bifunctor c, Diff2 b) =>
[D b X (Mu b y) y] ->
c (Z b X (Mu b y) y) (Z b Y (Mu b y) y) ->
c (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)
contextualize dXs = bimap
(\ (dX :<- XX t) -> yOnDown (dX : dXs) t)
(\ (dY :<- YY y) -> MuCx {aboveY = dXs, belowY = dY} :<-: y)
Für jede Y
Position müssen wir einen Element-Reißverschluss angeben, daher ist es gut, dass wir den gesamten Kontext dXs
bis zur Wurzel kennen und wissen , dY
wie sich das Element in seinem Knoten befindet. Für jede X
Position gibt es einen weiteren Teilbaum zu erkunden, also vergrößern wir den Stapel und machen weiter!
Damit bleibt nur die Verlagerung des Fokus. Wir könnten sitzen bleiben oder von dort, wo wir sind, hinuntergehen oder hinaufgehen oder hinauf und dann einen anderen Weg hinuntergehen. Hier geht.
aroundF z@(MuCx {aboveY = dXs, belowY = dY} :<-: _) = MuCx
{ aboveY = yOnUp dXs (In (up VY (zZipY z)))
, belowY = contextualize dXs (cxZ $ around VY (zZipY z))
} :<-: z
Wie immer wird das vorhandene Element durch den gesamten Reißverschluss ersetzt. Für den belowY
Teil schauen wir uns an, wohin wir sonst im vorhandenen Knoten gehen können: Wir werden entweder alternative Elementpositionen Y
oder weitere X
Unterknoten finden, die wir untersuchen contextualise
können , also wir sie. Zum aboveY
einen müssen wir X
uns nach dem Zusammenbau des Knotens, den wir besucht haben , wieder auf den Stapel der Derivate vorarbeiten.
yOnUp :: Diff2 b => [D b X (Mu b y) y] -> Mu b y ->
[D b X (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)]
yOnUp [] t = []
yOnUp (dX : dXs) (t :: Mu b y)
= contextualize dXs (cxZ $ around VX (dX :<- XX t))
: yOnUp dXs (In (up VX (dX :<- XX t)))
Bei jedem Schritt des Weges können wir entweder an einen anderen Ort abbiegen around
oder weiter nach oben gehen.
Und das ist es! Ich habe keinen formellen Beweis für die Gesetze erbracht, aber es sieht für mich so aus, als ob die Operationen den Kontext beim Crawlen der Struktur sorgfältig korrekt beibehalten.
Was haben wir gelernt?
Differenzierbarkeit führt zu Vorstellungen von Dingen in ihrem Kontext, wodurch eine komonadische Struktur entsteht, in extract
der Sie das Ding erhalten und duplicate
den Kontext erkunden, um nach anderen Dingen zu suchen, die kontextualisiert werden können. Wenn wir die geeignete Differentialstruktur für Knoten haben, können wir eine Differentialstruktur für ganze Bäume entwickeln.
Oh, und es ist schrecklich, jede einzelne Arität eines Typkonstruktors einzeln zu behandeln. Der bessere Weg ist, mit Funktoren zwischen indizierten Sätzen zu arbeiten
f :: (i -> *) -> (o -> *)
wo wir o
verschiedene Arten von Strukturen herstellen, in denen i
verschiedene Arten von Elementen gespeichert sind . Diese sind geschlossen unter dem Jacobi - Bau
J f :: (i -> *) -> ((o, i) -> *)
Dabei ist jede der resultierenden (o, i)
Strukturen eine partielle Ableitung, die Ihnen erklärt, wie Sie ein i
Elementloch in einer o
Struktur erstellen. Aber das ist für ein anderes Mal abhängig getippter Spaß.