345 Stimmen

Missbrauch der Algebra von algebraischen Datentypen - warum funktioniert das?

Der Ausdruck "algebraisch" für algebraische Datentypen sieht für jemanden mit einem Hintergrund in Mathematik sehr suggestiv aus. Lassen Sie mich versuchen zu erklären, was ich meine.

Nach der Definition der Grundtypen

  • Produkt
  • Gewerkschaft +
  • Singleton X
  • Einheit 1

und die Verwendung der Abkürzung para X•X et 2X para X+X usw., können wir dann algebraische Ausdrücke für z. B. verknüpfte Listen definieren

data List a = Nil | Cons a (List a) L = 1 + X • L

und binäre Bäume:

data Tree a = Nil | Branch a (Tree a) (Tree a) T = 1 + X • T²

Nun ist mein erster Instinkt als Mathematiker, mit diesen Ausdrücken durchzudrehen und zu versuchen, zu lösen L et T . Ich könnte dies durch wiederholte Substitution erreichen, aber es scheint viel einfacher zu sein, die Notation fürchterlich zu missbrauchen und so zu tun, als könnte ich sie nach Belieben umstellen. Zum Beispiel für eine verknüpfte Liste:

L = 1 + X • L

(1 - X) • L = 1

L = 1 / (1 - X) = 1 + X + X² + X³ + ...

wobei ich die Potenzreihenentwicklung von 1 / (1 - X) auf völlig ungerechtfertigte Weise ein interessantes Ergebnis ableiten, nämlich dass ein L Typ ist entweder Nil oder er enthält 1 Element, oder er enthält 2 Elemente, oder 3, usw.

Noch interessanter wird es, wenn wir dies für binäre Bäume tun:

T = 1 + X • T²

X • T² - T + 1 = 0

T = (1 - (1 - 4 • X)) / (2 • X)

T = 1 + X + 2 • X² + 5 • X³ + 14 • X + ...

wiederum unter Verwendung der Potenzreihenentwicklung (die mit Wolfram Alpha ). Damit wird die (für mich) nicht offensichtliche Tatsache ausgedrückt, dass es nur einen Binärbaum mit einem Element gibt, 2 Binärbäume mit zwei Elementen (das zweite Element kann auf dem linken oder rechten Zweig liegen), 5 Binärbäume mit drei Elementen usw.

Meine Frage ist also: Was mache ich hier? Diese Operationen scheinen ungerechtfertigt zu sein (was genau ist eigentlich die Quadratwurzel eines algebraischen Datentyps?), aber sie führen zu sinnvollen Ergebnissen. Hat der Quotient zweier algebraischer Datentypen irgendeine Bedeutung in der Informatik, oder handelt es sich nur um einen Notationstrick?

Und, was vielleicht noch interessanter ist, ist es möglich, diese Ideen zu erweitern? Gibt es eine Theorie der Algebra der Typen, die z. B. beliebige Funktionen auf Typen zulässt, oder benötigen Typen eine Potenzreihendarstellung? Wenn man eine Klasse von Funktionen definieren kann, hat dann die Komposition von Funktionen irgendeine Bedeutung?

152voto

C. A. McCann Punkte 76279

Disclaimer: Vieles davon funktioniert nicht wirklich richtig, wenn man es berücksichtigt, also werde ich das der Einfachheit halber einfach mal außer Acht lassen.

Ein paar erste Punkte:

  • Beachten Sie, dass "Vereinigung" hier wahrscheinlich nicht der beste Begriff für A+B ist - das ist speziell a disjunkt Gewerkschaft der beiden Typen, denn die beiden Seiten sind unterschiedlich, auch wenn ihre Typen gleich sind. Der gebräuchlichere Begriff ist übrigens einfach "Summentyp".

  • Singleton-Typen sind im Grunde alle Einheitstypen. Sie verhalten sich bei algebraischen Manipulationen identisch und, was noch wichtiger ist, die Menge der vorhandenen Informationen bleibt erhalten.

  • Wahrscheinlich wollen Sie auch einen Nulltyp. Haskell bietet das als Void . Es gibt keine Werte, deren Typ Null ist, genauso wie es einen Wert gibt, dessen Typ Eins ist.

Es fehlt noch eine wichtige Operation, aber darauf komme ich gleich zurück.

Wie Sie wahrscheinlich bemerkt haben, neigt Haskell dazu, Konzepte aus der Kategorientheorie zu entlehnen, und all das oben Gesagte lässt sich sehr einfach als solches interpretieren:

  • Gegebene Objekte A und B in Hask ist ihr Produkt A×B der einzige (bis auf Isomorphismus) Typ, der zwei Projektionen erlaubt fst : A×B A und snd : A×B B, wobei bei einem beliebigen Typ C und Funktionen f : C A, g : C B können Sie die Paarung definieren f &&& g : C A×B derart, dass fst (f &&& g) \= f und ebenso für g . Die Parametrisierung garantiert automatisch die universellen Eigenschaften, und meine nicht ganz so subtile Namenswahl sollte Ihnen die Idee vermitteln. Die (&&&) Operator ist definiert in Control.Arrow Übrigens.

  • Das Dual des obigen Produkts ist das Koprodukt A+B mit Injektionen inl : A A+B und inr : B A+B, wobei bei einem beliebigen Typ C und Funktionen f : A C, g : B C, können Sie die Kopaarung definieren f ||| g : A+B C so, dass die offensichtlichen Äquivalenzen gelten. Auch hier garantiert die Parametrizität die meisten der schwierigen Teile automatisch. In diesem Fall sind die Standardinjektionen einfach Left et Right und die Kopaarung ist die Funktion either .

Viele der Eigenschaften von Produkt- und Summentypen lassen sich aus dem oben Gesagten ableiten. Beachten Sie, dass jeder Singleton-Typ ein terminales Objekt von Hask und jeder leere Typ ist ein Ausgangsobjekt.

Um auf die oben erwähnte fehlende Operation zurückzukommen, wird in einer kartesische geschlossene Kategorie Sie haben Exponentialobjekte die den Pfeilen der Kategorie entsprechen. Unsere Pfeile sind Funktionen, unsere Objekte sind Typen mit Art * und der Typ A -> B verhält sich tatsächlich wie B A im Zusammenhang mit der algebraischen Manipulation von Typen. Falls es nicht offensichtlich ist, warum das so sein sollte, betrachten Sie den Typ Bool -> A . Bei nur zwei möglichen Eingaben ist eine Funktion dieses Typs isomorph zu zwei Werten des Typs A d.h. (A, A) . Für Maybe Bool -> A haben wir drei mögliche Eingaben, und so weiter. Wenn wir die obige Definition des Copairings in algebraischer Schreibweise umformulieren, erhalten wir die Identität C A × C B \= C A+B .

In Bezug auf warum dies alles Sinn macht - und insbesondere, warum Ihre Verwendung der Potenzreihenentwicklung gerechtfertigt ist -, beachten Sie, dass sich ein Großteil der obigen Ausführungen auf die "Bewohner" eines Typs bezieht (d. h. auf eindeutige Werte, die diesen Typ haben), um das algebraische Verhalten zu demonstrieren. Um diese Perspektive zu verdeutlichen:

  • Der Produkttyp (A, B) repräsentiert jeweils einen Wert aus A et B unabhängig genommen. Für jeden festen Wert gilt also a :: A gibt es einen Wert des Typs (A, B) für jeden Einwohner von B . Dies ist natürlich das kartesische Produkt, und die Anzahl der Einwohner des Produkttyps ist das Produkt der Anzahl der Einwohner der Faktoren.

  • Der Summentyp Either A B repräsentiert einen Wert aus entweder A o B wobei zwischen dem linken und dem rechten Zweig unterschieden wird. Wie bereits erwähnt, handelt es sich um eine disjunkte Vereinigung, und die Anzahl der Einwohner des Typs Summe ist die Summe der Anzahl der Einwohner der Summanden.

  • Der Exponentialtyp B -> A stellt eine Abbildung von Werten des Typs B auf Werte des Typs A . Für jedes feste Argument b :: B einen beliebigen Wert von A zugewiesen werden kann; ein Wert vom Typ B -> A wählt für jede Eingabe eine solche Abbildung aus, die einem Produkt aus ebenso vielen Kopien von A como B hat Einwohner, daher die Potenzierung.

Obwohl es zunächst verlockend ist, Typen als Mengen zu behandeln, funktioniert das in diesem Kontext nicht sehr gut - wir haben disjunkte Vereinigung statt der Standardvereinigung von Mengen, es gibt keine offensichtliche Interpretation der Schnittmenge oder vieler anderer Mengenoperationen, und wir kümmern uns normalerweise nicht um die Zugehörigkeit zu einer Menge (das überlassen wir dem Typ-Prüfer).

Andererseits wird in den oben genannten Konstruktionen sehr viel Zeit darauf verwendet, über Zählung Einwohner, und Aufzählung die möglichen Werte eines Typs ist hier ein nützliches Konzept. Das führt uns schnell zu enumerative Kombinatorik und wenn Sie den verlinkten Wikipedia-Artikel konsultieren, werden Sie feststellen, dass eines der ersten Dinge, die er tut, die Definition von "Paaren" und "Vereinigungen" in genau demselben Sinne wie Produkt- und Summentypen ist, und zwar mit Hilfe von erzeugende Funktionen und macht dann dasselbe für "Sequenzen", die mit Haskells Listen identisch sind, indem es genau dieselbe Technik wie Sie verwendet.


Edita: Oh, und hier noch ein kleiner Bonus, der meiner Meinung nach den Punkt eindrucksvoll verdeutlicht. Sie haben in einem Kommentar erwähnt, dass für einen Baumtyp T = 1 + T^2 können Sie die Identität ableiten T^6 = 1 was eindeutig falsch ist. Wie auch immer, T^7 = T する gelten, und eine Bijektion zwischen Bäumen und Siebener-Tupeln von Bäumen kann direkt konstruiert werden, vgl. Andreas Blass' "Sieben Bäume in einem" .

Bearbeiten×2: Zum Thema "Ableitung eines Typs", das in anderen Antworten erwähnt wurde, könnte Ihnen auch Folgendes gefallen dieses Papier desselben Autors das die Idee weiter ausbaut und Begriffe wie Division und andere interessante Dinge enthält.

53voto

sigfpe Punkte 7776

Binäre Bäume sind definiert durch die Gleichung T=1+XT^2 im Semiring der Typen. Durch Konstruktion, T=(1-sqrt(1-4X))/(2X) ist durch die gleiche Gleichung im Semiring der komplexen Zahlen definiert. Da wir also die gleiche Gleichung in der gleichen Klasse algebraischer Strukturen lösen, sollte es eigentlich nicht überraschen, dass wir einige Ähnlichkeiten feststellen.

Der Haken an der Sache ist, dass wir, wenn wir über Polynome im Semiring der komplexen Zahlen nachdenken, typischerweise die Tatsache nutzen, dass die komplexen Zahlen einen Ring oder sogar ein Feld bilden, so dass wir Operationen wie die Subtraktion verwenden, die für Semirings nicht gelten. Wir können aber oft Subtraktionen aus unseren Argumenten eliminieren, wenn wir eine Regel haben, die es uns erlaubt, von beiden Seiten einer Gleichung zu streichen. Dies ist die Art von Dingen, die durch Fiore und Leinster die zeigen, dass viele Argumente über Ringe auf Semiringe übertragen werden können.

Das bedeutet, dass viele Ihrer mathematischen Kenntnisse über Ringe zuverlässig auf Typen übertragen werden können. Infolgedessen können einige Argumente, die komplexe Zahlen oder Potenzreihen (im Ring der formalen Potenzreihen) betreffen, auf völlig rigorose Weise auf Typen übertragen werden.

Aber die Geschichte hat noch mehr zu bieten. Es ist eine Sache, zu beweisen, dass zwei Typen gleich sind, indem man z. B. zeigt, dass zwei Potenzreihen gleich sind. Aber man kann auch Informationen über Typen ableiten, indem man die Terme in den Potenzreihen untersucht. Ich bin mir nicht sicher, wie die formale Aussage hier lauten sollte. (Ich empfehle Brent Yorgey's Papier en kombinatorische Arten für einige Arbeiten, die eng verwandt sind, aber die Arten sind no dasselbe wie Typen).

Ich finde es umwerfend, dass das, was Sie entdeckt haben, auf die Infinitesimalrechnung übertragen werden kann. Theoreme über den Kalkül können auf den Semiring von Typen übertragen werden. Sogar Argumente über endliche Differenzen können übertragen werden, und man stellt fest, dass klassische Theoreme aus der numerischen Analysis Interpretationen in der Typentheorie haben.

Viel Spaß!

24voto

newacct Punkte 114757

Es sieht so aus, als ob Sie lediglich die Wiederholungsbeziehung erweitern.

L = 1 + X • L
L = 1 + X • (1 + X • (1 + X • (1 + X • ...)))
  = 1 + X + X^2 + X^3 + X^4 ...

T = 1 + X • T^2
L = 1 + X • (1 + X • (1 + X • (1 + X • ...^2)^2)^2)^2
  = 1 + X + 2 • X^2 + 5 • X^3 + 14 • X^4 + ...

Und da die Regeln für die Operationen auf den Typen wie die Regeln für arithmetische Operationen funktionieren, kann man mit algebraischen Mitteln herausfinden, wie man die Rekursionsbeziehung erweitert (da sie nicht offensichtlich ist).

24voto

Oly Punkte 2049

Calculus und Maclaurin-Reihen mit Typen

Hier noch ein kleiner Zusatz - ein kombinatorischer Einblick in die Frage, warum die Koeffizienten in einer Reihenentwicklung "funktionieren" sollten, wobei der Schwerpunkt auf Reihen liegt, die sich mit Hilfe der Taylor-Maclaurin Ansatz aus dem Kalkül. NB: Die von Ihnen genannte Beispielserienerweiterung des Typs "manipulierte Liste" ist eine Maclaurin-Reihe.

Da sich andere Antworten und Kommentare mit dem Verhalten von algebraischen Ausdrücken (Summen, Produkte und Exponenten) befassen, wird in dieser Antwort auf dieses Detail verzichtet und der Schwerpunkt auf den Typ "Kalkül" gelegt.

Sie werden feststellen, dass Anführungszeichen in dieser Antwort eine wichtige Rolle spielen. Dafür gibt es zwei Gründe:

  • es geht darum, Entitäten aus einem anderen Bereich zu interpretieren, und es scheint angemessen, solche Fremdbegriffe auf diese Weise abzugrenzen.
  • Einige Begriffe können strenger formalisiert werden, aber die Form und die Ideen scheinen wichtiger zu sein (und benötigen weniger Platz zum Schreiben) als die Details.

Definition der Maclaurin-Reihe

En Maclaurin-Reihe einer Funktion f : ist definiert als

f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f(0)X + ...

何所 f bedeutet die n th Ableitung von f .

Um die Maclaurin-Reihe bei der Interpretation mit Typen verstehen zu können, müssen wir verstehen, wie wir drei Dinge in einem Typenkontext interpretieren können:

  • eine (möglicherweise mehrfache) Ableitung
  • Anwendung einer Funktion auf 0
  • Begriffe wie (1/n!)

und es stellt sich heraus, dass diese Konzepte aus der Analyse passende Entsprechungen in der Welt der Schrift haben.

Was verstehe ich unter einem "geeigneten Gegenstück"? Es sollte den Geschmack eines Isomorphismus haben - wenn wir die Wahrheit in beiden Richtungen bewahren können, können Fakten, die in einem Kontext ableitbar sind, auf den anderen übertragen werden.

Kalkül mit Typen

Was bedeutet also die Ableitung eines Typenausdrucks? Es stellt sich heraus, dass es für eine große und wohlbehaltene ("differenzierbare") Klasse von Typausdrücken und Funktoren eine natürliche Operation gibt, die sich ähnlich genug verhält, um eine geeignete Interpretation zu sein!

Um die Pointe zu verderben: Die Operation, die der Differenzierung entspricht, ist die Herstellung von "Ein-Loch-Zusammenhängen". Diese ist ein hervorragender Ort, um diesen Punkt weiter zu vertiefen, aber das Grundkonzept eines Ein-Loch-Kontextes ( da/dx ) ist, dass er das Ergebnis der Extraktion eines einzelnen Untereintrags eines bestimmten Typs ( x ) aus einem Begriff (vom Typ a ), wobei alle anderen Informationen, einschließlich derjenigen, die zur Bestimmung des ursprünglichen Standorts des Unterpostens erforderlich sind, erhalten bleiben. Eine Möglichkeit, einen Ein-Loch-Kontext für eine Liste darzustellen, sind beispielsweise zwei Listen: eine für die Elemente, die vor dem extrahierten Element lagen, und eine für die Elemente, die danach kamen.

Die Motivation für die Identifizierung dieses Vorgangs mit der Differenzierung ergibt sich aus den folgenden Beobachtungen. Wir schreiben da/dx den Typ der Ein-Loch-Kontexte für den Typ a mit Loch vom Typ x .

d1/dx = 0
dx/dx = 1
d(a + b)/dx = da/dx + db/dx
d(a × b)/dx = a × db/dx + b × da/dx
d(g(f(x))/dx = d(g(y))/dy[f(x)/a] × df(x)/dx

Hier, 1 et 0 stehen für Typen mit genau einem bzw. genau null Einwohnern, und + et × wie üblich Summe und Produkttypen darstellen. f et g werden verwendet, um Typfunktionen oder Typausdruckserzeuger darzustellen, und [f(x)/a] ist der Vorgang der Ersetzung f(x) für jeden a im vorhergehenden Ausdruck.

Dies kann in einem punktfreien Stil geschrieben werden, indem man f' die Ableitungsfunktion vom Typ Funktion f also:

(x  1)' = x  0
(x  x)' = x  1
(f + g)' = f' + g'
(f × g)' = f × g' + g × f'
(g  f)' = (g'  f) × f'

was möglicherweise vorzuziehen ist.

NB: Die Gleichheiten können rigoros und exakt gemacht werden, wenn wir Ableitungen unter Verwendung von Isomorphieklassen von Typen und Funktoren definieren.

Nun stellen wir fest, dass die Regeln der Infinitesimalrechnung für die algebraischen Operationen der Addition, Multiplikation und Komposition (oft als Summen-, Produkt- und Kettenregeln bezeichnet) genau durch die Operation des "Löcherns" wiedergegeben werden. Außerdem sind die Grundfälle des "Löcherns" in einem konstanten Ausdruck oder dem Term x selbst verhalten sich auch wie Differenzierung, so dass wir durch Induktion ein differenzierungsähnliches Verhalten für alle algebraischen Ausdrücke erhalten.

Jetzt können wir die Differenzierung interpretieren, was bedeutet die n die 'Ableitung' eines Typausdrucks, de/dx bedeuten? Es handelt sich um einen Typ, der n -Ortskontexte: Begriffe, die, wenn sie "gefüllt" werden mit n Bezeichnungen der Art x einbringen e . Es gibt eine weitere wichtige Beobachtung in Bezug auf (1/n!) ' kommt später.

Der invariante Teil eines Typ-Funktors: Anwendung einer Funktion auf 0

Wir haben bereits eine Interpretation für 0 in der Welt der Typen: ein leerer Typ ohne Mitglieder. Was bedeutet es aus kombinatorischer Sicht, eine Typfunktion auf ihn anzuwenden? Konkreter ausgedrückt, nehmen wir an f eine Typfunktion ist, was bedeutet f(0) aussehen? Nun, wir haben sicherlich keinen Zugang zu irgendetwas von der Art 0 so dass alle Konstruktionen von f(x) die eine x sind nicht verfügbar. Was übrig bleibt, sind die Begriffe, die in ihrer Abwesenheit zugänglich sind, was wir den "invarianten" oder "konstanten" Teil des Typs nennen können.

Ein explizites Beispiel hierfür ist die Maybe Funktor, der algebraisch dargestellt werden kann als x 1 + x . Wenn wir dies anwenden auf 0 erhalten wir 1 + 0 - es ist einfach wie 1 : der einzig mögliche Wert ist der None Wert. Bei einer Liste erhalten wir ebenfalls nur den Begriff, der der leeren Liste entspricht.

Wenn wir sie zurückbringen und den Typ interpretieren f(0) als Zahl kann man sie sich als die zählen wie viele Begriffe des Typs f(x) (für jede x ) kann ohne Zugang zu einer x : das heißt, die Anzahl der "leerähnlichen" Begriffe.

Zusammenstellung: Vollständige Interpretation einer Maclaurin-Serie

Ich fürchte, mir fällt keine angemessene direkte Interpretation von (1/n!) als Typ.

Wenn wir jedoch den Typ f(0) In Anbetracht der obigen Ausführungen lässt sich feststellen, dass sie als die Art der n -Platzkontexte für einen Begriff des Typs f(x) die enthalten nicht bereits eine x - das heißt, wenn wir sie "integrieren n mal, hat der resultierende Begriff genau n x s, nicht mehr und nicht weniger. Dann ist die Interpretation des Typs f(0) als eine Zahl (wie die Koeffizienten der Maclaurin-Reihe von f ) ist einfach eine Zählung, wie viele solcher leeren n -Platz-Kontexte gibt. Wir sind fast am Ziel!

Aber wo ist (1/n!) enden? Die Untersuchung des Prozesses der "Differenzierung" des Typs zeigt uns, dass bei mehrfacher Anwendung die "Reihenfolge", in der die Unterbegriffe extrahiert werden, erhalten bleibt. Betrachten wir zum Beispiel den Begriff (x, x) vom Typ x × x und den Vorgang des "Löcherns" zweimal. Wir erhalten beide Sequenzen

(x, x)    (_, x)    (_, _)
(x, x)    (x, _)    (_, _)
(where _ represents a 'hole')

obwohl beide von demselben Begriff stammen, denn es gibt 2! = 2/code> ways to take two elements from two, preserving order. In general, [there are `n!`](https://en.m.wikipedia.org/wiki/Permutation) ways to take `n` elements from `n`. So in order to get a count of the number of configurations of a functor type which have `n` elements, we have to count the type `f(0)` and divide by `n!`, _exactly_ as in the coefficients of the Maclaurin series.

``

So dividing by n! turns out to be interpretable simply as itself.

Final thoughts: 'recursive' definitions and analyticity

First, some observations:

  • if a function f : has a derivative, this derivative is unique
  • similarly, if a function f : is analytic, it has exactly one corresponding polynomial series

Since we have the chain rule, we can use implicit differentiation, if we formalise type derivatives as isomorphism classes. But implicit differentiation doesn't require any alien manoeuvres like subtraction or division! So we can use it to analyse recursive type definitions. To take your list example, we have

L(X)  1 + X × L(X)
L'(X) = X × L'(X) + L(X)

and then we can evaluate

L'(0) = L(0) = 1

to obtain the coefficient of in the Maclaurin series.

But since we are confident that these expressions are indeed strictly 'differentiable', if only implicitly, and since we have the correspondence with functions , where derivatives are certainly unique, we can rest assured that even if we obtain the values using 'illegal' operations, the result is valid.

Now, similarly, to use the second observation, due to the correspondence (is it a homomorphism?) with functions , we know that, provided we are satisfied that a function has a Maclaurin series, if we can find any series at all, the principles outlined above can be applied to make it rigorous.

As for your question about composition of functions, I suppose the chain rule provides a partial answer.

I'm not certain how many Haskell-style ADTs this applies to, but I suspect it is many if not all. I have discovered a truly marvellous proof of this fact, but this margin is too small to contain it...

Now, certainly this is only one way to work out what is going on here and there are probably many other ways.

Summary: TL;DR

  • type 'differentiation' corresponds to 'making a hole'.
  • applying a functor to 0 gets us the 'empty-like' terms for that functor.
  • Maclaurin power series therefore (somewhat) rigorously correspond to enumerating the number of members of a functor type with a certain number of elements.
  • implicit differentiation makes this more watertight.
  • uniqueness of derivatives and uniqueness of power series mean we can fudge the details and it works.

``

19voto

yatima2975 Punkte 6540

Ich habe keine vollständige Antwort, aber diese Manipulationen funktionieren in der Regel "einfach so". Ein relevantes Papier könnte sein Objekte der Kategorien als komplexe Zahlen von Fiore und Leinster - Darauf bin ich beim Lesen gestoßen sigfpe's Blog zu einem verwandten Thema Der Rest des Blogs ist eine wahre Fundgrube für ähnliche Ideen und lohnt einen Blick darauf!

Sie können übrigens auch Datentypen unterscheiden - so erhalten Sie den passenden Zipper für den Datentyp!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X