Gut, also der Schritt von den relationalen Datenbanken zu den deduktiven Datenbanken,
in denen wir also in der Lage sind, neue Fakten aus alten Fakten abzuleiten.
Wir haben uns bestimmte Relationstypen angeguckt, wenn wir also Relationen mit bestimmten Eigenschaften erzeugen wollen,
was es in typische Pattern, mit denen wir das erreichen können.
Also beispielsweise, wenn wir eine symmetrische Relation haben wollen,
was müssen wir tun, um sie symmetrisch zu machen?
Was müssen wir tun, um eine Relation transitiv zu machen?
Was müssen wir tun, um zu verhindern,
dass eine Relation reflexiv ist und Das haben wir noch nicht behandelt,
aber schon mal angedeutet, dass man also auch Relationen,
die funktional in ein oder eindeutig in einem der Argumente sind,
dass man auch das erreichen kann, wenn man das denn möchte.
Standardmäßig ist es nicht der Fall. Und jetzt machen wir
den zweiten Schritt und das ist eigentlich der Spannende dieser beiden Schritte,
dass wir jetzt zu dem Mechanismus der dededuktiven Datenbanken noch rekrusive
Datenstrukturen mit hinzunehmen und dann auf der Ebene sind,
dass wir ein Turing mächtigen Berechnungsformalismus haben,
mit dem man alles brechnen kann,
was man berechnen
prinzipiell berechnen kann
Das erscheint erst mal relativ spartanisch, die Ausstattung, die wir haben.
Also wir nehmen relationale Datenbanken plus Regeln und den Deduktionsbegriff plus rekrusive Datenstrukturen.
Und das soll dann alles sein, was berechnungsuniversell ist.
Aber es ist tatsächlich der Fall. Was wir uns als erstes angucken,
sind also Strukturen, die in Strukturen eingebettet sind.
Das ist nämlich der entscheidende Frühschritt der Rekursion, den wir jetzt machen.
Wir hatten ja schon den anderen Fall der Rekursion,
dass wir eine Prädikatsdefinition wieder auf die gleiche Prädikatsdefinition zurückführen,
was wir brauchten, um beispielsweise Transitivität zu erreichen.
Jetzt geht es also darum, Faktoren in Strukturen einzubetten.
Eine zweite Form von Rekursion, die uns hier begegnet.
Und wir werden als erstes Anwendungsfeld da für uns anschauen,
wie man jetzt tatsächlich komplexe Programme schreiben kann,
indem wir die Arrithmetik, mit der wir alle vertraut sind,
und zwar Arrithmetik der natürlichen Zahlen, mithilfe relationaler Programmierung rekonstruieren.
Und wir gucken uns an, was für Vor- und was für Nachteile damit verbunden sind.
Wir machen dann einen kleinen Exkurs in die Syntax der Sprache
Indem wir uns Operator, Operatoren anschauen, sowohl die Frage,
was für Operatoren sind im System standardmäßig eingebaut.
Da haben sie eine große Anzahl schon kennengelernt.
Also beispielsweise der Größenvergleich, wenn ich zwei Zahlen aufgröße,
die Größenordnung miteinander vergleichen will, dann ist das ein Operator,
so ein taktisch, ein Operator.
Wir werden sehen, dass diese Operatoren nichts anderes sind als eine alternative,
syntaktische Notationsweise für die Strukturen,
die wir ohnehin schon kennen
Das sind zwei Sichten, die man nur aus Bequemlichkeit einführt.
Also wir kommen jetzt langsam mit dem Bereich, wo es um die,
um die Bequemlichkeit der Programmierung geht und nicht mehr so sehr um den grundlegenden Mechanismus.
Und abschließend wäre ich diesen Bereich dann damit,
dass wir uns natürlich angucken, wie man die Arrithmetik funktional machen kann,
aus guten Gründen ist, im Produktsystem eben eine Möglichkeit eingebaut,
dass man Arrithmetik nicht nur relational betreiben kann,
sondern auch funktional betreiben kann, nämlich genau aus dem Grunde,
dass man den eingebauten numerischen Prozessor auch tatsächlich benutzen möchte.
Und der ist nun mal funktional und deshalb müssen wir also
da eine spezielle Eine spezielle Umgebung dafür schaffen,
dass wir dort mit funktionaler Auswertung arbeiten können.
So, was wir also jetzt machen,
es sind ein paar kleine Erweiterungen der Syntax,
das ist aber nicht allzu viel, was neu hinzukommt.
Ich habe das hier rot markiert. Das ist der,
oh, ja, ich habe es rot markiert.
Auf meinem Bildschirm ist es rot. Ich weiß nicht,
ist das der Beamer oder sind das die Pins der,
das ist der Beamer, ja?
Das ist ja toll. Okay, gut,
dann muss ich ihnen erzählen,
was rot gefärbt ist
Also der wesentliche Schritt steckt hier drin, dass wir eine Struktur,
wie wir das bisher auch immer hatten, erklären,
als bestehend aus einem Namen und optional einer Liste von Thermen,
die durch Kommas getrennt sind.
Und ein Therm kann jetzt wieder eine Struktur sein, das ist das Neue.
Das kommt jetzt hinzu. Und dadurch kann ich Strukturen rekrusiv
ineinander einbetten und beliebig komplexe Datenstrukturen aufbauen auf die Art und Weise.
Das ist der erste wesentliche Schritt, den wir machen,
dass also hier eine Rekursion entsteht,
eine Struktur enthält Therme, Therme können wieder Strukturen sein.
Da ist der Zyklus
Es sind noch ein paar weitere Kleinigkeiten,
aber das sieht man jetzt sowieso nicht. Die Farben,
wir führen hier alternativ zu der Strukturnotation mit Name vorn,
gefolgt von der Liste der Argumentstellen, führen wir auch noch Operatorenausdrücke ein.
Als Alternative, Schreibweise für diesen Teil,
das ist die zweite Innovation und die dritte Innovation,
es gibt einen speziellen Typ eingebetteter Datenstrukturen, der sehr,
sehr häufig benutzt wird und sehr praktisch ist,
das ist die Liste.
Die führen wir auch als neues Element hier ein.
Genauer gesagt für Freunde der Java-Programmierung,
es ist eine verkettete Liste,
von der
wir hier sprechen
Weil verkettete Listen sind rekrusive Datenstrukturen. Und wir gucken uns
das eben als Beispiel für rekrusive Datenstrukturen in der Logikprogrammierung an.
Also der entscheidende Schritt ist, Strukturen können rekrusiv eingebettet sein,
können wieder Strukturen enthalten.
Und damit verlassen wir den Bereich der Normalform-Datenbanken.
Bisher haben wir uns also immer über Datenbank enthalten,
die wenigstens in der ersten Normalform waren.
Das ist jetzt nicht mehr der Fall.
Das sind dann sogenannte Non-F-F-First-Normal-Formen, Data-Bases.
Und ein Beispiel ist eben hier, dass ich solche Strukturen aufbauen kann,
ich habe jetzt ein Angebot bestehend aus einem Produkt einer Kategorie,
einem Status und einem Preis.
Und wir sehen auch hier schon eine Möglichkeit,
die auf der nächsten Folie nochmal diskutiert wird,
dass wir dies auch benutzen können,
um Typ sicheres Programmieren in einer Programmiersprache zu machen,
die eigentlich keine strenge Typisierung kennt.
Aber wenn wir es möchten, können wir es natürlich in die Hand nehmen.
Der wichtige Spezialfall, den werden wir uns heute noch
ausgiebig angucken, ist die reklusive Selbsteinbettung einer Struktur.
Also hier sind ja immer in die Struktur mit dem Namen Angebot sind,
ist eine Struktur mit dem Namen Produkt eingebettet,
mit dem Namen Kategorie und so weiter.
Das ist also kein Selbstein Selbstbezüglichkeit,
der interessante Spezialfall ist natürlich die Selbstbezüglichkeit,
wo ich in eine Struktur mit dem Namen S wieder eine
Struktur mit dem Namen S einbette und nach innenbeliebiger Tiefe.
Und wir gucken uns das auch schon mal an.
Hier ist eine einstellige Struktur rekrusiv in sich eingebettet,
mit der werden wir uns heute befassen.
Das hier ist eine zweistellige Struktur rekrusiv in sich eingebettet.
Erkennt jemand,
was es ist
Ja, das ist eine verkettete Liste. Eine Liste besteht immer
aus gepunkteten Paaren und deshalb habe ich das Link DP genannt,
für Dotted Pär und ein Dotted Pär hat immer ein erstes
Element und ein zweites Element, was die Restliste ist.
Und das rekrusiv eingebettet über die gesamte Länge der Liste.
Also das ist die interne Repräsentation für Listen in Prolog.
Und wir gucken uns dann noch alternativ dazu,
aber das ist erst Gegenstand des nächsten Abschnittes.
Alternativ dazu eine spezielle Syntaxangabe. Nein, die haben wir uns schon angeguckt.
Das Ergebnis Was uns das Find All beispielsweise liefert,
ist die Notation, die wir üblicherweise verwenden werden.
Aber intern steckt so etwas dahinter. Nur das Ding,
nicht Lotted Pierre heißt, sondern wirklich, der Funktor ist der Punkt.
Da wird eine Struktur mit dem Punkt als Namen kreiert und
zwei Argumenten jeweils für den K und den CDR der Liste.
Ich hatte schon angedeutet, wir können also solche eingebettete Strukturen verwenden,
indem wir jetzt, damit wir typsicheres Programmieren machen können,
also typisches Beispiel ist ja immer, sind Preisangaben,
wo nie ganz klar ist, wenn dort nur eine Nummer steht,
In welcher Währung sind die Preisangaben?
Geschwindigkeitsangaben, wo nie ganz klar ist,
in welcher Maßeinheit sind, die genau wie Entfernungsangaben,
wo ich nicht weiß, ob das Meilen oder Kilometer sind,
wenn es nicht explizit genannt wird und in einem globalisierten Kontext
macht das natürlich große Probleme mit solchen Angaben.
Das findet man an allen Ecken und Enden,
also gehen sie beispielsweise mal nach Äthiopien,
die messen die Uhrzeit anders als wir.
Bei denen fängt der Tag früh um sechs Uhr an mit
Stunde Null und geht dann eben bis abends um sechs Stunde zwölf.
Also wenn man dort sagt, wir treffen uns um sechs,
dann ist nach unserer Zeit mittags um zwölf gemeint.
Und nicht früh um sechs. Oder abends um sechs.
Und solche Informationen muss man also hinzuliefern,
in welchem metrischen System sind solche Angaben jetzt formuliert.
Das Gleiche gilt dort übrigens auch für die Jahresangaben.
Die haben nach wie vor ihren alten äthiopischen Kalender,
der gegenüber unserem um ein paar Jahre verschoben ist.
Also wenn man da nicht explizit dazu sagt,
ist das äthiopischer Kalender oder ist es der Grigorianische,
den wir verwenden,
dann kommt es zu bösen Missverständnissen
Und das kann man eben dadurch erledigen,
dass man hier den Typ als Funktor außen drumherum schreibt und
dann natürlich die gesamte Verarbeitung darauf zuschneidet,
dass nur Dinge miteinander verknüpft werden, die den gleichen Typ haben.
Ansonsten scheitert eben die Unifikation, warum, da kommen wir
gleich drauf und dann kann man damit eben nichts anfangen.
Hier also ein Beispiel dafür, wo ich jetzt eben explizit mache,
dass ich hier einen, in der Anfrage,
dass ich über einen Preis in Euro spreche und der Preis muss dann kleiner als 200 sein.
Das kann man da natürlich auch noch erweitern auf das
Type Casting, dass man also die Typumwandlung gleich mit vorsieht
Die andere Anwendung kennen Sie auch, ist Datenabstraktion,
dass ich, das ist das,
was ich üblicherweise in Objektrelationalen Datenbanken mache,
dass ich nicht mehr atomare Informationen abspeichere, sondern komplexe,
strukturierte Informationen abspeichere, dass ich also eine Information für die Adresse habe,
die dann aufgeteilt ist in die einzelnen Bestandteile der Adresse,
also Ortpost-Deckzahl und Straßenangabe.
Interessanterweise gibt es hier keine Hausnummer.
Spannend, vergessen
Gut, eine solche rekursiv eingebettete Struktur kann ich immer als Baumstruktur
interpretieren und Bäume spielen in der Informatik an vielen Stellen ja eine ganz,
ganz zentrale Rolle.
Die eine Struktur entspricht dann immer der Verzweigung eines Baumes,
also genau den Knoten des Baumes.
Der Name der Struktur ist dann immer die Knotenmarkierung.
Der äußerste Name, den ich habe, ist der Wurzelknoten des Baumes.
Ich habe hier immer den gleichen Fehler gemacht,
den sie auch gerne machen.
Das Ding Prädikatsname genannt. Das sind natürlich keine Prädikate,
die dort stehen,
sondern es sind Strukturen
Prädikate werden sie ja erst dadurch, dass sie in
der Datenbank stehen. Aber solange ich sie irgendwie zwischen Prozeduren
hin und her reiche, sind es noch keine Prädikate.
Also hier sollte lieber Strukturnahme stehen als Prädikatsnahme.
Der äußerste Name ist dann der Wurzelknoten des Baumes.
Die Argumentstellen sind die Kanten des Baumes. Und wenn ich
Atome als Argument habe, dann sind das die Knoten,
die Blattknoten des Baumes.
Und das drückt man uns hier mal an zwei Beispielen an.
Das erste ist eine Typ, eine Teil von Hierarchie.
Wir hatten ja bereits Teil von Hierarchien im Zusammenhang mit den
deduktiven Datenbanken.
Hier ist eine alternative Repräsentation da
Wo ich also nicht sage, A,
ist unmittelbares Teil von B und B ist unmittelbares Teil von C,
sondern wo ich in einer Struktur diese Information zusammenfasse.
Also ein Auto besteht eben hier aus drei Bestandteilen,
Rad, Motor und Karosse. Und das Rad besteht wieder
aus zwei Bestandteilen, nämlich Felge und Reifen.
Und auf die Art und Weise kann ich solche strukturellen Informationen auch darstellen.
Und wir sehen eben, der äußerste Name dieser Struktur ist der Spitzenknoten des Baumes.
Jeder Name einer Struktur ist immer wieder ein Knoten des Baumes.
Auch dann, wenn er keine Argumentliste danach hat.
Also Zylinder ist ja nullstelliger Funktor. Das heißt, das
ist dann ein Blattknoten des Baumes. Da verzweigt der Baum
sich nicht weiter. Und die Anzahl der Argumentstellen hier ist eben der Verzweigungsgrad,
dass der Motor eben drei Teile als drei Komponenten als Bestandteil hat.
Auf die Art und Weise kann ich mir also solche
hierarchisch strukturierten Informationen in einer formalen Struktur kodieren und weiterverarbeiten.
Sowohl in den Programmiersprachen als auch in der natürlichen Sprache verwendet man Bäume zur Darstellung der internen Struktur von Ausdrücken,
die also angeben, im Falle der Programmiersprachen,
wie ich die Einen Ausdruck auswerten soll oder wie die Verarbeitung eines Ausdruckes passieren soll,
wodurch ich insbesondere deutlich machen kann,
in welcher Reihenfolge bestimmte Operatoren angewendet werden müssen.
Das gucken wir uns im Zusammenhang mit den Operatoren nochmal genauer an.
In der natürlichen Sprache gibt es mir eine Interpretationsvariante für den Satz vor.
Also diese Struktur hier heißt eben, ich bin ein Satz,
bestehend aus einer Nominalphrase und einer Verbalphrase.
Die Nominalphrase ist einfach nur der Name John. Die Verbalphrase
ist das Verblikes und die Nominalphrase hier hinten ist Mary.
Und für das Englische gilt dann eben,
dass das Subjekt des Satzes hier vorn steht.
Also John ist derjenige, der etwas tut und Mary ist
diejenige,
mit der
etwas getan wird
Halten Sie sich nur vor Augen, fürs Deutsche gilt das nicht.
Also wenn ich sage, John liebt Marie,
dann ist nicht klar, wer dort wen liebt.
Ich kann das durch Betonung deutlich machen und kann sagen,
John liebt Marie. Nicht Hans. Da sind also die Interpretationen,
ist die Interpretationsvariante wesentlich größer, weil Deutsch eben wenigstens eine
partiell anordnungsfreie Sprache ist.
Jedenfalls, was die Position von Subjekt und Objekt betrifft.
Es gibt Sprachen, da ist dies wesentlich weitergetrieben.
Also die slavischen Sprachen etwa, da kann ich aus der
Position des Wortes nicht erkennen,
welche Bedeutung es hat
Und dann muss ich eben Mechanismen auffahren,
die in der Lage sind, diese Strukturbeschreibung zu abzuleiten,
sodass ich dann auf der Basis der Strukturbeschreibung sagen kann,
ja hier, das ist das Subjekt,
der macht etwas und das ist das Objekt, mit dem wird irgendetwas gemacht.
In den Programmiersprachen hat man das,
also eben auch hier am Beispiel von arithmetischen Ausdrücken,
also Dinge, wo ich rechne, numerische Auswertung betreibe,
man kann einen solchen Ausdruck,
das sollte ihn schon mal über den Weg gelaufen sein,
in verschiedenen Notationsvarianten dahinschreiben,
das hier ist eine Infix-Notation,
wo immer der Operator zwischen seinen Operanten steht
Heißt also, hier soll 3 mit 4 addiert werden.
Das kann ich auch in eine Prä-fix-Notation umwandeln. Die Prä-fix-Notation ist
hier angegeben. Da steht eben der Operator von und die Operanten
kommen danach in einer Liste zusammengefügt. Und dann sieht der gleiche Ausdruck,
der hier in Infix-Notationen gegeben ist, in Prä-fix-Notationen so aus,
als oberster Operator ist diese Multiplikation auszuführen, zu multiplizieren,
ist das Ergebnis dieser Addition und das Ergebnis dieser Subtraktion,
das steht hier unten.
Und die Subtraktion hat eben als ersten Operanten
einen neuen und als zweiten Operator wieder einen arithmetischen Ausdruck,
nämlich 3 mal 2. Das ist der, der hier unten steht.
Ich kann das Ganze auch in Postfick-Postfix-Motationen aufschreiben.
Postfick-Notation-Notation findet sich auch heute noch in vielen Anwendungsbereichen,
weil sie nämlich relativ einfach zur Abarbeitung ist.
Ich habe immer erst die Werte, mit denen ich etwas
machen will. Und wenn ich alle Werte zusammen habe,
dann kommt der Operator und sagt, was ist zu tun?
Und beispielsweise Postscript ist genau auf dieser Idee aufgebaut. Postscript-Ausdrücke sind immer Postfickers Notationen,
wo erst die Operanten kommen und zum Schluss der Operator,
der sagt, was jetzt tatsächlich zu berechnen ist.
Ja, gut, also auch hier haben wir eben die Möglichkeit,
mithilfe dieses Syntaxbaumes jetzt auszudrücken, in welcher Reihenfolge sind welche Operationen durchzuführen.
Das, was hier durch die Klammern ohnehin deutlich gemacht worden ist,
was wir aber in den, hier zum Beispiel nicht ganz offensichtlich ist,
in welcher Reihenfolge dies durchzuführen ist,
da gibt es dann Präzedenzregeln der Art,
Punktrechnung geht vor Strichrechnung.
Ich muss also erst diese Multiplikation ausführen,
ehe ich dann das Ergebnis von der neuen subtrahieren kann
Und genau das ist hier in dem Baum ausgedrückt.
Es muss erst drei und zwei miteinander multipliziert werden.
Und dann kann ich das Ergebnis dieser Multiplikation von neuen subtrahieren.
Und ich muss erst hier drei und vier addieren,
ehe ich dann von diesem Ergebnis, das Ergebnis dieser Auswertung abziehen kann,
nicht abziehen kann, indem ich das multiplizieren kann.
Auf diese Art und Weise ist mir also vorgegeben,
wie die, oder zwar explizit vorgegeben,
wie die Abarbeitung zu erfolgen hat,
während es hier eben noch implizit ist und erst durch ihn
Zunahme zusätzlicher Auswertungsregeln
erschlossen werden kann
Du, wir haben jetzt einen neuen Datentyp eingeführt,
den wir bisher noch nicht hatten. Wir hatten zwar bisher
schon Strukturen, aber die Strukturen waren immer nur elementar.
Also es war genau nur eine Ebene, bestehend Ausnahme und
der Attributliste. Jetzt haben wir eingebettete Strukturen und dann müssen
wir also unseren Unifikationsbegriff zum dritten Mal erweitern.
Ursprünglich waren es eben nur Konstanten und Barjablen,
dann sind die Strukturen hinzugekommen und jetzt kommen also rekursiv eingebettete Strukturen hinzu.
Die dritte Erweiterung des Unifikationsbegriffs. Es bleibt dabei,
wir müssen den Namen miteinander vergleichen und schauen,
ob das identisch ist
Die Stelligkeit muss überprüft werden, dass wie bisher auch,
das hat sich nichts geändert. Und das Einzige,
was hinzukommt, ist nicht, bisher stand da,
dass die Argumente unifiziert werden müssen, hier muss jetzt rekursiv unifiziert werden.
Wenn ich also an einer Attributposition ein Therm vorfinde,
dann muss ich denen wieder der Unifikation unterwerfen.
Also Unifikation ist jetzt ein rekursiver Prozess,
der sich durch die Struktur durchhangelt,
Ebene für Ebene und jeweils überprüft,
ob diese Bedingungen hier erfüllt sind und dann,
ob die jeweiligen Attributwerte auch wieder miteinander und infizieren können
Also, wir haben die Situation, eine Struktur trifft auf
eine Struktur, dann müssen wir das miteinander unifizieren.
Den Fall hatten wir auch schon. Also,
wir müssen den Funk dann miteinander vergleichen,
wir müssen die Stelligkeit überprüfen, ist zweihöfstellig in beiden Fällen.
Wir unifizieren die erste Position mit der zweiten Position und halten eine vaiablen,
Quatsch, wir unifizieren die erste Position in dem ersten Term
mit der ersten Position in dem zweiten Term,
unterhalten eine vaiablen Bindung für X und wir
unifizieren die zweiten Positionen miteinander,
unterhalten eine vaiablen Bindung für
Gut, das ist also nichts Neues, was hinzugekommen ist.
Die spannenden Fälle sind also tatsächlich die rekursiven Fälle. Also,
das ist der Fall, den wir bereits angeguckt hatten.
Jetzt kommen hier also mal rekursiv eingebettete Strukturen zur Verfügung, zur Anwendung.
Ich habe hier eine zweistellige Struktur mit einer Co-Referenz drin.
Und die soll ich unifizieren mit dieser zweistelligen Struktur.
Und dann ist eben die Abarbeitung jetzt,
und das muss man stur durchhalten, da hilft nichts,
ich muss die Stelligkeit überprüfen, ich muss den Funktor überprüfen,
das ist alles okay.
Ich unifiziere dieses X mit D von Y und stelle damit
eine Variablenbindung für X her. Und mit dieser Variablenbindung muss
ich jetzt in die Unifikation der zweiten Argumentposition einsteigen.
Also D von Y wird jetzt mit D von B unifiziert.
Und durch diese Unifikation, die hier ausgeführt wird,
wird jetzt das Y auf dem Wert B gesetzt
Das ergibt sich also über diese Co-Referenz, wodurch der Therm
hier auf der ersten Argumentposition indirekt über die Chor-Referenz mitnimmt,
Therm auf der zweiten Argumentposition unifiziert werden muss.
Und okay, sorry, keiner schreit auf,
das ist natürlich nicht D von B,
sondern D von kleinen B gewesen.
Das macht einen wesentlichen Unterschied. Hier, das dritte Beispiel,
hier haben wir auch wieder eine Chor-Referenz drin.
Wir haben jetzt einen Ausdruck hier vorn mit einer Variablen X,
die mit einem mit der Variable Y und unifiziert werden muss.
Neues Beispiel, ne? Eine Unifikation mit der Variablen Y
und mit dieser Wertebindung für Y geht es jetzt in die
Unifikation auf der zweiten Argumentposition und dort wird jetzt Y auf A von X gesetzt.
So, damit ergibt sich diese Wertebindung sowohl X als auch
Y haben jetzt den Wert A von X. Sieht jemand,
was hier passiert
Dann werden wir nur auf das RPG,
den ersten und den letzten Teil des Aufnahmewegs ist gleich A von X. Das heißt,
wir haben keine richtige Distanzierung, weil in der Erklärung,
ich sage, nenne es mal Erklärung,
eine Erklärung für X, wieder das X-Level-Pokal.
Ja, also es ist eine Struktur, so viel können wir sagen.
Es ist eine Struktur, die hat einen Funktor A,
sie ist einstellig. Ich habe einen Wert hier drin,
eine Variable hier drin und das ist wieder eine Struktur.
A, die einstellig ist und die hat weder einen Wert,
dass es eine Struktur A,
die einstellig ist und so geht das weiter in die Tiefe
bis ins Unendliche
Wir haben also hier erstmalig einen Blick in den Abgrund der Unendlichkeit getan.
Und wir werden noch des Öfteren Blicke in diese Abgründe tätigen müssen.
Wir haben eine endliche Struktur, nämlich dieses hier,
die tatsächlich einen unendlich großen Datenausdruck beschreibt,
den wir nicht mehr ausdrucken können.
Also in dem Augenblick, wo ich das auf die Konsole
schicke, terminiert es nicht mehr.
Frühere Prologsysteme haben an dieser Stelle angefangen zu flackern,
weil sie nämlich versucht haben,
dieses Ding auszudrucken
In der Zwischenzeit hat man verschiedene Varianten eingeführt,
um der Sache zu begegnen. Der erste Mittel,
das erste Mittel war, dass man eine maximale Tiefe angegeben hat,
bis zu der der Ausdruck noch ausgegeben wird.
Das ist im SWE-Prolog auch immer noch der Fall. Dann konnte man also sicherstellen,
ich drucke, sagen wir mal, die ersten zehn Rekursionsebenen aus,
danach breche ich ab, mache nur noch Punkte rein,
andeutend, dass es so weitergeht, wie bisher auch.
Neuerdings gibt es ein neues Feature, das so aussieht,
dass wenn ich diese Unifikation fordere,
also X soll gleich A von X sein,
dann kriege ich als Ergebnis dies auf der Konsole ausgegeben
Und das ist eine Notation dafür,
dass im Untergrund tatsächlich eine endlos rekursive Datenstruktur existiert.
Ich kriege sie auf der Konsole nur nicht zu sehen,
aber ich kann rauskriegen, dass es tatsächlich der Fall ist.
Also ich kann diese Anfrage hier ausgeben und damit praktisch fordern,
ja, sage mir bitte den Wert von X.
Also das erste Ding, was in eingebettet ist.
Es ist nicht A selber, sondern was in A eingebettet ist.
Dann sage mir den Wert über B,
was zweimal in X eingebettet ist
Und dann sage ich mir über C, was dreimal in
X eingebettet ist. Und das Spiel könnte ich fortsetzen.
Ich könnte sagen, gib mir das Vierfach eingebettete Element,
das Fünffach, das Sechsfach, das Siebenfach eingebettete Element.
Und wir sehen, was immer wir fordern, ob erstes,
zweites oder drittes Rekursionsebene, die Dinger sehen immer gleich aus.
Ich kann beliebig in die Tiefe gehen, der Ausdruck ändert sich nicht.
Es ist ein unendlich großer Ausdruck, der da unten steht.
Die Frage ist nur, wie kann ich damit umgehen,
sodass also mein System nicht abstürzt?
Und das ist eine der Möglichkeiten, die man erfunden hat,
dass man für solche Endlosrekursiv eingebetteten Therme einen solchen Ausdruck verwendet.
Also selbstbzüglicher Rekursion in einem Therm
An der Stelle sollte man vielleicht sagen,
dass dies in den Begriffen der Logik eigentlich nicht erlaubt ist.
Eine Unifikation im Sinne der mathematischen Logik darf nicht erfolgreich sein,
wenn sie solche Therme erzeugt, die sich selbst wieder als Variable enthalten.
Das ist nicht erlaubt. Das kann man verhindern,
indem man einen sogenannten Occurse-Check einbaut, der testet,
ob dieser Fall vorliegt, dass eine Struktur sich selbst wieder
als Wert enthält.
Die praktischen Prologsysteme verzichten aber alle auf den Occurse-Check
Weil er auch ein bisschen Aufwand hervorruft und die ganze Sache etwas verlangsamt,
die Berechnung, weil er nämlich immer gemacht werden muss.
Er muss bei jeder Unifikation gemacht werden. Er ist zwar
nur Linne A im Aufwand der Größe der Strukturen,
aber trotzdem, es ist Aufwand, der erzeugt wird.
Und das im Grunde zichtet man da drauf.
Es ist in einigen Prologsystemen auch sogar zur Philosophie erhoben worden.
Das ist gerade das Ziel ist, mit solchen Strukturen umgehen
zu können. Und es gibt durchaus Anwendungsbeispiele,
das können wir im Falle der Listen vielleicht nochmal diskutieren,
wo ich zyklische Listen sinnvoll verwenden kann
Sie ja mal darüber nachdenken, ob es da,
ob denen da was einfällt dazu. Gut,
also wir haben hier eben Co-Referenz über mehrere Rekursionsebenen hinweg.
In diesem Fall ist es nur über eine Rekursionsebene hinweg.
Also der Ausdruck findet sich sofort wieder auf der nächsten Ebene.
Und dann habe ich eben unendliche Strukturen aufgebaut, die,
nein, also ja, eine unendliche Struktur aufgebaut,
indem ich endliche Strukturen miteinander unifiziert habe.
Also das ist sozusagen, das sollte man immer im Hinterkopf haben.
Hier passiert ein qualitativ neu, etwas qualitativ Neuwertiges.
Ich stecke zwei endliche Strukturen in die Unifikation hinein.
Ja, dieses hier. Und ich kriege eine unendliche heraus
Gut, wir gucken uns aber lieber erstmal nicht die
etwas problematischen Fälle an, sondern die Standardfälle.
Also wir haben Unifikationen erweitert auf die Behandlung rekursiv eingebetterter Datenstrukturen.
Und wir müssen natürlich auch unsere Semantik, die hat sich nicht verändert.
Nein, es ist nur eine syntaktische Information, die hat keine Semantik.
Die Pragmatik des Ganzen ist ein, können wir jetzt besser beleuchten.
Wir hatten ja schon gesehen, dass wir Anfragen ganz an unterschiedlich
instanziert erstellen können und das wirkt sich jetzt an dieser Stelle aus.
Wir können, die Anfrage wird ja über Unifikation verarbeitet und
so wie Unifikation definiert ist, kann sie dreierlei Funktionen gleichzeitig erfüllen.
Das sind Dinge, die etwa in der objektorientierten Programmierung sauber voneinander getrennt sind.
Sie sind bei uns in einer einzelnen Operation vereinigt und wir
können sie nicht mehr auseinander definieren.
Was genau passiert, hängt immer von der Instanziierungsvariante ab.
Wir können also Unifikation verwenden, um Tests durchzuführen,
also beispielsweise zu testen, ist der Preis gleich 100,
der da drin steht in der Struktur oder mit anderen Worten,
sind zwei Strukturen miteinander verträglich.
Das sind also typischerweise Strukturen,
die keine Vagablen enthalten
Wir können es als Ghetta, or Accessor, Observer,
oder es gibt so viele Namen dafür, eine objektorientierten Programmierung,
verwenden, indem ich in die Anfrage eine Variable hineinstecke.
Und dann ist das gleichbedeutend mit der Frage,
aus welchen Bestandteilen setzt sich eigentlich eine komplexe Struktur zusammen?
Oder gib mir einen bestimmten Teil aus dieser Struktur
als Ergebnis oder hole mir den als Ergebnis heraus?
Und wir können es als Konstruktor verwenden,
wir können Strukturen nehmen und in neue Strukturen zusammenbauen
Und dadurch beliebig komplexe Datenstrukturen erzeugen. Und diese verschiedenen Verwendungsweisen
sollten wir jetzt immer im Hinterkopf haben, wenn wir da rangehen,
tatsächlich Anwendungspunktbekrame zu schreiben. Das, was jetzt kommt,
ist relationale Arrithmetik, das ist noch nicht wirklich ein Anwendungsprogramm,
sondern das dient im Wesentlichen der Illustrationen,
wie ich mit solchen Datenstrukturen umgehen kann,
was man mit denen alles machen kann.
Und da die natürlichen Zahlen, das einfachste Beispiel einer
rekrusiven Struktur sind, nehme ich die natürlichen Zahlen dafür.
Ein einfacheres gibt es nicht. Also es geht nicht weiter
zu reduzieren. Das ist das Einfachste, was wir haben
Und natürliche Zahlen sind eben etwas sehr Natürliches, die schon
seit Jahrtausenden bekannt sind. Aber was sie mathematisch tatsächlich bedeuten,
was eine natürliche Zahl eigentlich bedeutet,
das hat erst ein italienischer Mathematiker im Übergang vom 18. zum 19. Jahrhundert.
Äh, sorry, vom Übergang. Im Übergang vom 19.
zum 20. Jahrhundert herausgefunden. Und das ist Giseppe Piano gewesen
. Und nachdem sind die Dinger auch als Piano-Zahlen benannt.
Wir akkummatisieren jetzt also die natürlichen Zahlen in mathematisch präziser Form.
Mit den Begriffen der Logik
Und daher der Bezug zur Logikprogrammierung auch.
Wir definieren eine natürliche Zahl dadurch, dass wir sagen,
null ist eine natürliche Zahl. Und wir führen eine Nachfolgerbeziehung ein,
die sagt, jede natürliche Zahl besitzt einen unmittelbaren Nachfolger.
Der wird als Successor von N bezeichnet, also S von N.
Wir können jede beliebige andere Struktur nehmen, das ist egal,
sie müssen einstellig sein, das ist das Wesentliche.
Und dieser Nachfolger ist ebenfalls wieder eine natürliche Zahl.
Und die unmittelbare Konsequenz aus dieser Nachfolgerbeziehung ist,
es gibt unendlich viele natürliche Zahlen
Es gibt keine größte, ich kann zu jeder natürlichen Zahl wieder einen Nachfolger definieren.
Das ist der wesentliche Unterschied zu Inditure-Zahlen. Inditure-Zahlen haben immer
eine größte Inditure-Zahl. Natürliche Zahlen kennen keine Oberschranke, ja.
Aber diese Definition ermöglicht es ja auch, dass die natürlichen Zahlen zyklisch sind.
Wenn der Nachfolger erwerbt ist ja nicht. Es ist nicht ausgeschlossen,
dass sie, ja, das ist richtig, ja,
genau gut beobachtet, ja.
Das müsste man auch noch ausschließen, ja. Gut,
also der Domän hier Abschluss sagt, dass nur was so gebildet ist,
natürliche Zahlen sind, man müsste stärker einschränken und sagen,
dass eine Zahl sich nicht selbst als Nachfolger haben darf.
Das ist eine zusätzliche Bedingung. Wesentlich ist hier eben dieser rekursive Schritt,
dass sich die Definition einer natürlichen Zahl auf die Definition einer
natürlichen Zahl zurückführe und damit eine rekursive Datenstruktur vorliegen habe.
Wir müssen die irgendwie repräsentieren und ich hatte schon gesagt,
wir nehmen diesen Funktor S für Successor und damit hat ein PA-Notherm,
das ist jetzt keine spezielle Syntax,
die irgendwie im Prolog-System verwaltet wird,
sondern die definieren wir für unsere Zwecke.
Wir wollen einen Typ einführen,
ich bin ein PA-Notzahl
Der Typ ist eben dadurch beschrieben, dass ich sage,
ja, entweder die Null ist eine PA-No-Zahl oder aber es ist eine einstellige Struktur mit dem Namen S und die im Inneren wieder einen PA-No-Zahl enthält.
Und Semantik, das können wir dadurch regeln,
indem wir das auf unsere klassische Verwendungsweise von natürlichen Zahlen abbilden,
wir sagen, dieses Ding hier sei eben unsere Null,
wie wir sie klassischerweise kennen, dieses S von Null,
also der unmittelbare Nachfolger der Null ist die 1,
der Nachfolger, der des Nachfolgers der Null ist die 2,
der Nachfolger des Nachfolgers, des Nachfolgers,
der Null ist die 3.
Und so weiter bis ins Unendliche
Ja, und hier sehen wir also,
dass wir diese zyklische Einbettung brauchen, um über ein mathematisches
Konzept wie natürliche Zahlen überhaupt reden zu können im Computer.
Denn wir brauchen etwas, was keine obere Schranke hat und die Einbettungstiefe dieser Därme,
Modulo, der Speichergröße des Systems, ist natürlich nicht begrenzt.
So, also, das ist kein Prolo-Konstrukt, sondern eine spezielle Form von Prolo-Strukturen,
mit denen wir über die nächsten zwei Stunden arbeiten werden.
Und wir wollen jetzt hingehen und Programme schreiben,
die mit solchen PA-No-Zahlen operieren können und wir nehmen als erstes einen Typtest.
Wir wollen für diesen Typ,
den wir gerade so taktisch eingeführt haben,
einen Test schreiben, der uns überprüft,
bis du eine PA-No-Zahl oder bist du
keine PA-No-Zahl
Und dann brauchen wir einfach nur die Definitionen, die wir
eben hingeschrieben haben, nehmen, eine Null ist eine PA-Notzahl.
Punkt. Und wenn X eine PA-No-Zahl ist,
dann ist auch der Nachfolger von X eine PA-No-Zahl.
Das ist nichts anderes als die mathematische Definition der PA-No-Zahl,
die finden wir hier wieder.
Also die rekursive Definition der natürlichen Zahlen findet sich eben hier
jetzt wieder in dieser rekursiven Definition des Typtests.
Der Typtest macht ja nichts anderes als zerlege das Ding
und überprüfe, ob es eine PA-No-Zahl ist
Gut, an dieser Stelle habe ich normalerweise immer
ein paar Anschauungsmaterialien mit. Das hat heute nicht geklappt,
deshalb müssen wir das Ganze virtuell machen.
Sie wissen, was das ist. Sie wissen nicht,
was es ist. Doch, dann sagen Sie es,
was ist es? Das sind diese Figuren,
die selbst wieder Figuren teilen,
sie wieder Figuren
Okay, gut, die haben einen Namen im russischen Matroschka.
Und so unter dem Namen sind sie üblicherweise weltweit auch bekannt.
Für mich sind es PANO-Zahlen. Es ist nichts anderes als
eine PANO-Zahl hier. Was wir nämlich bei dieser Matroschka sehen,
von außen ist, es ist eine Matroschka.
Wenn man genau hinguckt, sieht man, dass es,
es gibt zwei Arten von Mertroschkas. Die einen,
die man öffnen kann und die anderen, die man nicht öffnen kann.
Jede gute, ausgestattete Matroschka enthält ganz innen ein Element,
was sich nicht mehr öffnen lässt. Wenn es wohlgeformt ist,
wenn es unseren Typtest besteht, Das,
wenn man genau hinguckt, sieht man,
dass da diese Grenze in der Mitte am Bauch ist,
wo die Trennlinie ist, wo ich das Ding aufmachen kann.
Ich kann also den Typtest anwenden und kann feststellen,
es ist eine Matroschka, von außen gesehen.
Es sieht wie eine Matroschka aus, aber ich weiß nicht,
ob es wirklich eine ist.
Warum nicht
Ja. Danke dir. Wie halte ich gerade raus,
kannst du dich kennen. Genau. Ich weiß nicht,
was da drin ist. Vielleicht ist es eine Zigarettenkippe drin
. Dann ist es keine ruhige Form in Matroschka.
Der einzige Weg festzustellen, ob es eine wohlgeformte Matroschka ist,
ist, sie auseinanderzunehmen und zu prüfen, rekursiv,
ob im Inneren sich wieder eine Matroschka verbirgt.
Und das so lange zu machen, bis sich beim Null-Element angekommen bin.
Das ist nämlich die innerste Matroschka, die sich nicht mehr zerteilen lässt.
Was ich eben nicht beantworten kann, ist die Frage,
wie sieht es im Inneren dieser Figur eigentlich aus?
Und insbesondere kann ich nicht beantworten die Frage,
wie viele Matroschkas sind da eigentlich drin in dem Bauch
Wenn ich also verschiedene Matroschkas gegeben habe,
wie hier beispielsweise, kann ich nicht sagen,
welche von denen größer oder kleiner als eine andere ist.
Wobei größer jetzt nicht im Sinne der Geometrie gemeint ist,
sondern im Sinne der Anzahl der Matroschkas,
die sich da drin noch verbergen.
Eine Matroschka, die im Laden steht, kann sich im Endeffekt
als Billigprodukt herausstellen und er hält nur zwei weitere Matroschkas.
Es kann aber auch eine Raffinierte sein, die noch 15 bei Thematroschkas enthält.
Ich kann dem Verkäufer glauben oder ich kann es nachprüfen wollen.
Wenn ich es nachprüfen will, muss ich sie auseinandernehmen.
Es gibt keinen anderen Weg dafür
Und das ist also egal, welche Formen- und Ausstattung Ponymatroschka-Mann
da im Auge hat. Also die Analogie, die strukturelle
Analogie zwischen Matroschka und Piano zahlen sollte einigermaßen deutlich sein.
Ich muss also hier am Typtest tatsächlich eine gegebene PA-Nutzahl zerlegen,
bis ich beim Null-Element angekommen bin und dann kann ich sicher sein,
ja, es handelt sich um eine PA-Nutzahl oder es handelt sich eben nicht um eine PA-Nutzahl.
Wir testen das natürlich, wie wir das gewöhnt sind,
an verschiedenen Test aufrufen. In diesem Fall nehmen wir also eine PA-No-Zahl,
der Nachfolger, der Nachfolger der Null und testen,
ob dies eine PA-No-Zahl ist.
Und wir kommen als erstes zu unserem Rekussionsabschluss,
nämlich dem Test auf das End-Element, die Null.
Das unifiziert nicht, weil wir hier eine einstellige Struktur S von irgendetwas haben.
Hier haben wir ein Atomvorliegen, da ist keine Unifikation möglich.
Also rein ins Spectracking, die nächste Alternative ist diese Regel.
Wir binden über den Kopf der Regel X1
an den Wert S von Null
Das ist diese Bindung hier. Und mit diesem Wert für X1
geht es in den Körper der Klausel und der wird aufgerufen.
Das heißt, der neue Rekursivaufruf ist, ist es null eine PA-Notzahl.
Oder mit anderen Worten, ich kann diese Anfrage bejahen,
wenn ich nachweisen kann, dass das wahr ist.
Um das nachzuweisen, dass das wahr ist,
muss ich wieder meine Definition von des Prädikats hernehmen.
Und komme als erstes zum Rekursionsabschluss. Auch hier klappt die Unifikation nicht,
das ist ein Atom, das ist eine einstellige Struktur,
Unifikation scheitert.
Die Alternative führt mir, gib mir eine neue Variablenbindung für
eine neue Variable,
die heißt jetzt
Und die kriegt den Wert null. Denn ich muss jetzt hier,
wie hier oben auch, diese Struktur mit dieser Struktur unifizieren.
Funktoessidentisch, Stelligkeit ist identisch. Hier trifft eine Variable auf
diese Konstante. Und damit wird X2 an null gebunden.
Und mit diesem Wert für X2 geht es in den Körper der Klausel.
Der wird rekursiv aufgerufen. Und jetzt teste ich,
ob die null eine PA-No-Zahl ist,
da sagt dieser Fakt mir, jawohl, ist sie.
Und ich kriege ein Yes heraus oder ein True heraus.
Die Frage ist, was passiert,
wenn ich nach Alternativen, wenn ich um Alternativen bitte?
Gibt es noch
eine Alternative
Hm? Kann es nicht geben, weil danach die Unifikation wieder herschlägt.
Okay, ich müsste mir die Regel angucken. Ja,
ich habe einen Aufruf von PA 00 und im Kopf der Regel steht eine einstellige Struktur,
dann trifft dieses Atom hier auf diese einstellige Struktur,
die Unifikation scheitert,
also über die alternative Regel hier gibt es keine weiteren Ergebnisse.
Es ist also tatsächlich eindeutig festgestellt worden,
dass es nur eine Möglichkeit gibt,
die Wahrheit dieses Ausdrucks oder des Ziels
hier oben nachzuweisen
Also was hier versteckt ist, ist eben der interessante Fall.
Wir müssen hier eine eingebettete rekursive Struktur mit einer eingebetteten rekursiven Struktur unifizieren.
Das ist der entscheidende Punkt. Das ist das, was Neue hinzugekommen ist.
Und dann müssen wir eben Funktor überprüfen, Stelligkeit überprüfen,
Funktor ist identisch, Stelligkeit ist in beiden Fällen eins.
Und wir müssen die Attributposition, die Argumentposition miteinander unifizieren.
Und dadurch wird dieses X1 an das, was in dem
ersten S-Ausdruck drin ist, nämlich S von null gebunden.
Das ist genau dieser rekursive Unifikationsprozess,
den wir gerade neu eingeführt haben
Ja, das Ganze nochmal im Trace. Ich habe also immer
zwei Klauseln. Mehr gibt es nicht zu diesem Typtest.
Ich nehme die erste Klausel, die Unifikation scheitert,
weil dieser Ausdruck hier drin nicht mit diesem Ausdruck unifiziert.
Ich nehme die Regel und unifiziere jetzt diesen S-Ausdruck mit diesem S-Ausdruck hier oben.
Und das führt dazu, dass das Innere des S-Ausdruckes,
in dem ich X1 hier und das Innere hier,
nämlich S von Null, aneinander gebunden werden.
Das ist diese Bindung, die wir hergestellt wird.
Und die ist dann auch der Wert für den Aufruf des
neuen Ziels aus dem Körper der Klausel
Das ist der neue Aufruf, der Fakt sagt,
die Null ist eine PA-Notzahl, die Unifikation hier dazwischen scheitert wieder.
Also muss ich die Regel hernehmen, da ist die Unifikation erfolgreich.
Ich habe einen S-Ausdruck, einstellig, X-2 wird an Null gebunden.
Das ist die Variablenbindung, die hier hinten steht und mit
dieser Variablenbindung geht es weiter. Jetzt trifft die Frage,
ist die Null eine PA-Notzahl auf den Fakt?
Die Null ist eine PA-Notzahl und das führten wir zu dem Ergebnis.
Weitere Ergebnisse haben wir nicht. Und wir können jetzt natürlich negative Tests machen,
wenn ich sage, ist Susi eine PA-Notzahl,
dann scheitert die Unifikation mit dem ersten Fakt.
Es scheitert aber auch die Unifikation mit dem Kopf dieser Regel.
Das heißt, keine dieser beiden Klauseln führt zu irgendeinem Ergebnis
und wir kriegen ein False heraus
Und das muss sich auf der obersten Ebene passieren.
Wenn ich also S von S von S von Susi
habe, ist genau der gleiche Effekt.
Ich baue erst mal drei Essschalen ab,
bis ich dann irgendwann im Inneren auf Susi stoße.
Und das ist weder mit dem Rekursionsabschluss noch mit dem Rekursionsschritt der Prädikatsdefinition verträglich.
Das Wesentliche ist hier dabei, dass hier eine rekursive Struktur
konsumiert wird. Wenn ich also diesen Rekursionenabstieg mache,
ich habe hier noch eine zwei, zweifach eingebettete Struktur.
Im nächsten Schritt habe ich nur noch eine einfach eingebettete Struktur.
Und zum Schluss habe ich gar keine eingebettete Struktur mehr
Also man sieht hier, die PA-No-Zahl wird konsumiert,
indem ich von außen immer Schale für Schale wegnehme.
Also Mattroschka auseinandernehmen, ja, ich nehme jeweils eine Schicht
Mattroschka weg und schaue, was im Inneren passiert.
Diese Konsumtion ist wesentlich dafür, dass das Ganze terminiert.
Also wir werden in Zukunft immer gucken müssen,
haben wir den Fall vorliegen, und zwar in allen Instanzierungsvarianten,
dass an irgendeiner Stelle eine rekrusive Struktur konsumiert wird.
Wenn das nicht der Fall ist, dann kriegen wir Terminierungsprobleme.
Und dann müssen wir möglicherweise sagen,
dieser Aufruf ist nicht sicher,
den darf man nicht verwenden
Und genau das müssen wir überprüfen. Deshalb also die Testmethodik,
dass wir immer rangehen und gucken, ja, hier ist es trivial hier.
Wir haben nur eine einzige Argumentposition. Aber wenn wir
mehr Argumentpositionen haben, müssen wir alle Instanzierungskombinationen überprüfen,
ob wirklich in jedem Falle gesichert ist, dass die Konsumption passiert.
Nur dann können wir sicher sein, dass das Ganze terminiert.
Jetzt ist ein unterspezifisierter Aufruf. Also wir rufen das Ganze
mit einer Variablen auf. Auch hier ist es trivial,
es ist die einzige Möglichkeit, das unterspezifiziert aufzurufen,
was anderes geht nicht.
Wir haben nur die eine Argumentposition
Und wenn wir jetzt die erste Klausel von der Prädikatsdefinition herhernehmen,
wird x an null gebunden und wir kriegen das erste Ergebnis ausgegeben.
Also auf die Frage, was sind PANO-Zahlen,
kriege ich als erstes Ergebnis, die null ist eine PANO-Zahl.
Wenn ich um Alternativen bitte, also nach weiteren Ergebnissen frage,
dann wird die Regel bemüht und die sagt, ja,
es von irgendetwas ist eine PANO-Zahl, wenn denn sich nachlässt,
oder wenn ich denn ein irgendetwas finden kann,
was eine PANO-Zahl ist.
Also kriege ich eine Variablenbindung, dieses X hier wird ein S von X1 gebunden.
Das Ergebnis sagt bereits, jedes Ding Was eine mit
einem S von irgendetwas anfängt, wird eine PA-No-Zahl sein.
Ich weiß nur noch nicht, wie das da innen drin aussieht.
Aber das ist ein ganz, ganz wesentlicher Schritt zu diesem Zeitpunkt der Abarbeitung.
Weiß das System bereits, wie das Ergebnis aussehen wird?
Im Prinzip, es kennt die Details noch nicht.
Aber das Prinzip lautet, es muss außen drumherum eine Essschale sein.
Was innen ist, das kriegen wir später. Aber ich
weiß an der Stelle schon, außen wird es wie eine Matroschka aussehen.
Ob es im Inneren auch eine Matroschka ist, das ist noch offen.
Und damit klingt ja jetzt also den Aufruf mit dieser vajablen Bindung Moment,
nein, mittig mit der Variablenbindung. Wir kriegen den Auftrag,
gib mir bitte eine PANO-Zahl, die ich hier einfüllen kann.
Also mache wieder einen uninstanzierten Aufruf von diesem Prädikat PANO.
Und das liefert mir mit dem Fakt als erstes wieder eine Variablenbindung.
Jetzt wird X1 auf Null gesetzt. Dieses X1,
den Wert von X1 kann ich hier rein substituieren.
Das heißt, das zweite Ergebnis ist X gleich erst von Null.
Jetzt kenne ich das Innere plötzlich. Vorher wusste ich nur,
außen die Erstschale muss es sein. Jetzt weiß ich,
wenn ich da eine Null einfüge, dann ist es auch eine PANO-Zahl.
Also das zweite Ergebnis, was ich rausbekomme, ist,
S von null ist auch eine PA-Notzahl. Wenn ich wieder nach Alternativen frage,
komme ich wieder in diese Regel hinein und die Regel sagt mir,
ja, wenn du denn irgendetwas findest,
was man hier reinfügen kann und was eine PA-Nutza ist,
dann ist X1 eine PA-Nutzahl und da X1 das Innere dieses Ausdrucks ist,
ist damit auch X von S, von S,
von irgendetwas eine PA-Nutzahl.
Und dieser uninstanzuierte Aufruf liefert mir das nächste Ergebnis und das
nächste Ergebnis ist wieder die Null, überraschenderweise,
das heißt S von S von Null ist eher die dritte PA-Nutzahl,
die ich geliefert bekomme.
Und ich kann natürlich wieder die Regeln aufrufen und dann werde
ich S von S von S von Null als nächste PA-Nutzahl.
Nein, sorry, das war einmal zu viel.
S von S von S von Null als nächste PA-Nutzahl bekommen.
Deutlich wird aber, solange ich das Spiel fortsetze,
jetzt gibt er den Geist auf, solange ich das Spiel fortsetze,
werde ich weitere PA-Nutzahlen bekommen.
Es gibt keine obere Schranke dafür. Also ich habe hier erstmalig die Möglichkeit,
unendlich viele Ergebnisse zu erzielen und die tatsächlich alle sinnvoll sind,
sind alles korrekte Antworten auf die Frage, welche PA-Nutzahlen gibt es.
Wir sehen das Ganze terminiert, solange ich es nicht
immer wieder ins Back-Tracking zwinge. Also insbesondere ein Versuch,
ein Find-All über dieses Prädikat zu machen, führt natürlich ins Chaos.
Dann versucht er unendlich viele Verano-Zahlen zu generieren und in eine Liste einzufügen.
Das sollte man typischerweise nicht machen. Aber solange ich die Kontrolle darüber habe,
wie viele Ergebnisse ich sehen möchte, terminiert das Ganze.
Und ich kann das eben dann auf die Art und Weise
das Prädikatschema allgemeiner, dass ich sage,
funktioniert sowohl für instanzierte Aufrufe als auch für Lichtinstanzierte Aufrufe.
Ich muss mir nur immer darüber im Klaren sein,
wenn ich es nicht instanziert aufrufe,
dann gibt es unendlich viele Ergebnisse.
Aber ich habe noch
die Kontrolle drüber
Ja, das Gleiche nochmal im Trace.
Also der Aufruf ist uninstanziert, was im PA nur zahlen.
Der Fakt liefert mir die erste Bindung von X an Null.
Das wird als Ergebnis ausgegeben. Die Frage nach Alternativen
ruft diese Klausel auf, die Klausel sagt, ja,
X von X wird es von irgendetwas sein.
Und dieses irgendetwas muss ich noch finden. Ich mache einen Aufruf,
um dieses irgendetwas zu finden. Und die erste Antwort ist wieder eine Null,
die ich jetzt als Vagablenbindung für X habe, sodass für X1 habe,
sodass also insgesamt die Variablen benug X gleich erst von Null,
sorry, hier ist sie als zweites Ergebnis produziert wird.
Und wenn ich wieder nach Ergebnissen frage,
dann wird das weder die Regel angewendet,
es wird eine weitere Essschale eingebaut und die dritte PA-No-Zahl produziert.
Und was wir hier sehen ist, im Gegensatz zu dem,
was wir vorher hatten, hier wird jetzt nicht konsumiert,
hier wird produziert.
Wir haben das gleiche Prädikat, völlig gleiche Implementation.
Es hat sich nichts geändert, außer die Aufrufvariante.
Einmal habe ich es spezifiziert aufgerufen. Ich habe ihm
eine PA-Natal gegeben. Jetzt rufe ich es unterspezifiziert auf.
Und dann sehen wir eben, wie eine Struktur rekursiv konstruiert wird.
Und zwar immer so, dass ich sie von außen nach innen aufbaue.
Das ist das Spannende daran. Und das werden wir ganz oft wiederfinden,
dass wir also wissen, dieses X wird schon ein S von irgendetwas sein.
Aber hier kann ich verschiedene Sachen reinfügen. Ich kann entweder
eine Null reinschreiben oder ich kann das Spiel weitertreiben und ein
weiteres S von irgendetwas einfügen.
Und das kann ich wieder entweder eine Null einfügen oder eine
weitere S-Struktur.
Und das
eben beliebe ich tief
Solange wir meine Geduld reicht. Also, die Unifikation,
da sehen wir es, ermöglicht ein und dasselbe Programm,
sowohl als Konstruktor,
als auch als Testoperation oder als Ghetter oder wie auch immer genannt,
zu verwenden.
Die Rekursion über so einer Struktur ist in solchen Spiegelbildern immer sehr schön dargestellt,
wo ich also im Spiegelbild wieder die gleiche Struktur vorfinde,
nur um etwas verkleinert.
Und genau das haben wir
hier auch vorliegen
Ich, das oberste Spiegelbild oder das oberste Bildebene entferne,
ist innen drin wieder die gleiche Art von Bild vorzufinden,
wie wir das eben auch bei den PA-Notalen haben.
Nur mit dem einen Unterschied, dass es hier hinten kein letztes Element gibt.
Wenn die Bildqualität ausreichend groß ist, geht das unendlich in die Tiefe.
Eine PA-Notzahl hat hinten irgendwann mal einen validen Rekursionsabschluss,
der sagt, ich bin das letzte Element dieser Zahl.
Gut, also,
wenn wir jetzt über einer rekursiven Datenstruktur ein rekursives Prädikat definieren,
dann sollten wir das immer machen unter Konsumtion einer Datenstruktur
auf eine Argumentposition
Und wir brauchen eine Terminierungsbedingung, das ist dann die Rekursionsabschluss für diese Struktur.
Und wir gucken uns dann einen wichtigen Spezialfall an,
das ist die Endrekursion, aber erstmal gucken wir uns nochmal die Definition an.
Genau das haben wir hier gemacht. Wir haben hier eine
Klausel, die konsumiert. Die nimmt eine komplexe PEANO-Zahl und
macht eine einfache PEANO-Zahl raus, indem sie eins abzieht.
Und wir haben für einen Abschluss des Ganzen gesorgt, über dieses erste,
über die erste Klausel. Das sind immer die wesentlichen Elemente,
die wir brauchen, um wirklich hilfreiche Definitionen zusammenzubekommen,
die dann tatsächlich auch unter den verschiedenen Instanzziierungsvarianten terminieren.
Endrekursion heißt jetzt also, dass die Berechnung nur beim rekursiven Abstieg erfolgt.
Und wir haben da schon gesehen, die Berechnung erfolgt in einer Weise,
dass in das Innere einer PANO-Zahl immer neue PANO-Zahlen hineingeschoben werden.
Das heißt, ich kenne das Äußere bereits. Alles,
was mir noch fehlt, sind die inneren Details.
Das heißt, dass die Berechnung erfolgt nur beim rekursiven Abstieg.
Es passiert im rekursiven Aufstieg, also Sie kennen das vielleicht
aus der funktionalen Programmierung, wo man immer unterscheidet.
Ich mache erstmal Entrekursiven Abstieg, bis ich am Rekursionsabschluss angekommen bin.
Und dann muss ich wieder einen rekursiven Aufstieg machen,
um das Ergebnis berechnen zu können
Der rekursive Aufstieg fällt in diesen Fällen hier vollständig weg.
Deshalb ist das ganze Prädikat hier Endrekursiv.
Das Endergebnis ist also unten am Rekursionsabschluss bereits vollständig bekannt.
Es wird nicht erst beim rekursiven Aufstieg zusammengestellt,
sondern es ist bereits vom ersten Schritt an bekannt,
dass das das Ergebnis sein wird.
Und wenn ich das so lange fortgeschrieben habe,
bis ich hier innen drin irgendwann mal die Null eintragen kann,
dann bin ich am Rekursionsabschluss angekommen und in dem Augenblick kenne ich das Berechnungsergebnis.
Das heißt also, ich muss nur diesen Abstieg machen,
kenne das Berechnungsergebnis und brauche mich über die Speicherverwaltung
überhaupt nicht mehr
zu kümmern
Also die Unterschied zwischen Entrekusiv und Nicht-Entrekusiv, den werden wir dann tatsächlich noch diskutieren.
Und darauf bezieht sich das ja. Ich muss mir die Zwischenergebnisse,
die ich brauchte, um den Rekusionsabschluss zu finden,
die muss ich mir gar nicht merken.
Ja, ich brauche, wenn es denn endrekusiv ist,
dann brauche ich immer nur den nächsten Schritt zu machen.
Und in dem Augenblick, wo ich unten angekommen bin,
habe ich das vollständige Ergebnis in der Hand.
Und wenn das der Fall ist,
dann kann ich das gesamte Programm kompilieren und das macht
das Produktsystem auch in einen iterativen Code,
der also gar nicht mehr über die Verwendung des Kellerspeichers arbeitet
Das kann man also automatisch erkennen. Wann liegt eine Endrekursion vor?
Und der Test ist also, sie liegt immer dann vor,
wenn der rekursive Aufruf das letzte Teilziel im Rekursionsschritt ist.
Und was können wir uns an der Definition wieder angucken?
Das ist in diesem Falle trivial. Wir haben im Rekursionsschritt nur ein Teilziel.
Das heißt, es muss das Letzte im Körper der Klausel sein.
Ein anderes ist nicht da. Und wir sehen also an
der äußeren Form bereits, dass dies eine Endrekursive Definition ist.
Und wenn wir hinter die Kulissen schauen,
wie die vajablen Bindungen hergestellt werden, sehen wir auch,
dass es tatsächlich so ist, dass man nur den rekursiven Abstieg braucht,
wenn man den Rekursionsabschluss gefunden hat,
ist die Berechnung vollständig abgeschlossen.
So, wir gucken uns mal noch ein paar komplexere Beispiele an.
Das erste ist, bis jetzt haben wir ja nur
einen Typ-Test gemacht, das ist ja nicht sonderlich spannend.
Das nächste Beispiel ist also der Vergleich zweier PA-No-Zahlen bezüglich ihrer Größenordnung.
Also die Frage, gegeben zweimal Troschkas,
welche enthält mehr innere Elemente als die andere?
Und wie gesagt, die äußere Geometrie ist dafür ziemlich irrelevant.
Also wir nehmen hier das kleine Alz,
kann aufgerufen werden mit zwei PA-No-Zahlen und soll uns also liefern
Welche davon ist die? Oder ist es wahr,
dass die erste kleiner als die zweite ist?
Wir machen jetzt hier eine methodischen Schritt,
der ihnen helfen soll, das deklarative Denken zu entwickeln.
Und es ist verträglich damit, was man auch in
anderen Bereichen der Softwareentwicklung üblicherweise versucht zu vermitteln.
Testbeispiele werden als erstes generiert. Ehe ich daran gehe,
eine Zeile Programmcode zu schreiben, schreibe ich mir erst auf,
was sind die Fälle,
mit denen ich gedenke,
dieses Ding mal später testen zu wollen
Und wir machen das so,
dass wir uns die Testfälle aufschreiben und dass wir uns durch Betrachten der Eigenschaften,
der Testfälle durch die Brille unserer Verarbeitungsmechanismen, die wir haben,
versuchen, die Definition abzuleiten.
Und das ist eine rein deklarative Vorgehensweise, die also nicht darauf schaut,
was muss ich in welcher Reihenfolge machen, sondern die nur darauf schaut,
was sind eigentlich die Gemeinsamkeiten und die Unterschiede dieser Testfälle,
die ich hier hinschreibe.
So, also da können wir loslegen.
Der erste Testfall ist, null ist kleiner als eins,
das sollte ein positiver Testfall sein.
Null ist kleiner als zwei, sollte auch ein
positiver Testfall sein
Null ist, sorry, eins ist kleiner als zwei,
sollte auch positiver Testfall sein. Und dann nehmen wir noch
ein paar negative Testfälle mit hinzu, also die Null,
kleiner als die Null, da sollte Falls rauskommen.
Die eins kleiner als die Null sollte auch Falls rauskommen und
die zwei kleiner als die eins sollte auch Falls rauskommen.
Ich hatte schon in der Übung gesagt, und hier glaube ich auch,
die Negation ist in diesem ganzen Rahmen ein Problemfall.
Nicht nur hier, das gilt grundsätzlich für die gesamte Programmierung.
Nur in den meisten Fällen wird man nicht explizit darauf hingewiesen.
Hier wird man explizit darauf hingewiesen,
dass Negation immer ein kritischer Fall ist,
weil typischerweise Kompliment zu einem positiven Fall unendlich viele Instanzierungen bedeuten würde.
Und diese unendlich vielen Instanzierungen, egal in welchem Paradigma ich befinde,
die kann ich auf einer endlichen Maschine nicht produzieren.
Deshalb ignorieren wir hier erstmal die negativen Fälle.
Für die Programmentwicklung schauen wir nur auf die positiven Fälle und
versuchen jetzt durch die Eigenschaften dieser Testbeispiele die Definition abzuleiten.
So, und dann sehen wir, können wir gucken,
gibt es Gemeinsamkeiten zwischen dem ersten und dem zweiten Testfall
Ich habe es gesehen,
aber ich möchte, das ist jetzt ein entscheidender Schritt.
Wie komme ich vom Testfeld zum Programm? Was sind die Gemeinsamkeiten
der ersten beiden Zeilen? Also wesentlich sind natürlich hier die Argumentbelegung,
dass das der gleiche Funktor hier vorn ist, das ist selbstverständlich.
Wir wollen das Präkat LT definieren. Aber was haben die
Argumentbelegungen hier gemeinsam? Ja. Das bin Juli immer kleiner
als ein Klianerzeit. Genau, also hier vorn,
die Null taucht in beiden auf und hier hinten,
das kann ich beschreiben,
hat die Gemeinsamkeit,
das ist immer S von irgendetwas
Nur unterscheidet sich, dass irgendetwas hier, von dem irgendetwas hier.
Und das kann ich hinschreiben. Null ist kleiner als es von irgendetwas.
Und damit habe ich die halbe Definition fertig. Ohne,
dass ich in irgendeiner Weise über prozedurale Abläufe nachgedacht habe.
So, jetzt gucken wir uns das zweite und das dritte Beispiel an.
Was sehen wir da, was gemeinsam ist? Sorry,
nicht das zweite, wir nehmen das erste und das dritte Beispiel.
Das zweite und das dritte wird schwierig.
Wir nehmen das erste und das dritte, die dritte Zeile.
Was ist denen gemeinsam
Ja. Das ist besser gemeint, das ist immer klein, das ist weiter. Ja,
das wollen wir rauskriegen, das ist das Ziel,
da wollen wir hin, klar, das ist,
das versteht sich, ja, ja, das jetzt anschauen,
wie ich das hinkriege von der dritten Seite,
ist exakt einmal das Infessor von der ersten,
das Recht ist genau das,
einmal ist das so vom Recht, dann kommt der jetzt.
Genau, wir können also hier ein S wegnehmen und landen bei dem,
ja, und wir können hier ein S wegnehmen und landen bei dem.
Also wir können diese Berechnung, auf diese Berechnung zurückführen
Genau, da schreiben wir hier hin. S von X
, das ist dieses hier. Und S von Y,
das ist dieses hier, stehen in der Relation kleiner als,
wenn denn X und Y in der Relation kleiner als stehen.
Also ich nehme hier ein Element weg,
ich nehme da ein Element weg und dann muss das,
was übrig bleibt, in der gleichen Relation stehen, wie das ursprüngliche.
Oder mit anderen Worten, ich habe zweimal Troschgas und ich weiß nicht,
welche mehr Elemente enthält, was ich machen muss, ist,
ich muss die erste Hülle in beiden Fällen abnehmen und schauen,
was innen drin bleibt.
Wenn innen drin sich herausstellt,
ich habe die letzte Matroschka in der Hand,
dann ist dieses Ding offensichtlich kleiner als das andere,
wo ich
noch weiter zerlegen kann
Wenn ich wieder zwei zerlegbare Matroschkas habe,
dann muss ich eben die Operation rekursiv wiederholen,
indem ich nochmal jeweils eine Hülle wegnehme und schaue,
ob ich jetzt bei dem Fall angekommen bin.
Und das mache ich eben genauso lange,
bis ich zwangsweise hier eine Null bekommen habe.
Oder aber ist irgendwann mal das Ganze nicht mehr unifiziert.
In welchem Fall würde es nicht mehr unifizieren?
Gerrit, er reizt zuerst mit der Null. Genau,
wenn ich hier bei der inneren Matroschka angekommen bin,
hier aber noch Essschalen da sind,
dann würde dieses Ding nicht mehr unifizieren.
Das übrigens auch nicht,
denn hier wird auch ein Ess von etwas verlangt
Und wenn also das Ding Null ist, unifiziert weder der erste,
die erste Klausel, noch der Kopf der zweiten Klausel.
Das heißt, dann gibt es kein Ergebnis.
Und das ist genau das, was wir haben wollen.
In diesem Falle ist die erste Zahl größer als die zweite
und dann sollte ein Fonds rauskommen.
Das ist genau das gewünschte Verhalten. Also sozusagen das negative
Verhalten ergibt sich hier aus den Beschränkungen der positiven Definition.
Wir schauen uns wieder Beispiele an,
wie das Ganze abgearbeitet wird und ob ihr prozedural auch wirklich
das erzielt haben,
was wir
deklarativ haben wollen
Also als erstes, wir machen einen vollständig spezifizierten Aufruf.
Dann wird auf beiden Datenstrukturen konsumiert. Das müssen wir jetzt
nicht mal durchdiskutieren. Das sollte eigentlich klar geworden sein.
Interessant sind jetzt die Unterspezifizierten Aufrufe. Also dieser erste Fall,
beides sind PANO-Zahlen, das diskutiere ich jetzt nicht mehr.
Und das spezifiziert heißt, ich sage, welche Zahlen sind kleiner
als die zwei? Das heißt, diese Anfrage hier.
Ich nehme als erstes die erste Klausel. Ach,
übrigens ist dies ein nettes Beispiel und glaube,
das erste Beispiel, was wir haben,
wo ein Fakt mit einer Variablen
drin vorkommt
Ja, das ist keine Grundstruktur mehr, die wir hier haben.
Hier ist eine eingebettete Struktur und im Inneren dieser Struktur steckt eine Variable drin.
Ich glaube, das ist das erste Mal, dass das passiert.
Es fällt mir jetzt erst auf. Das sollte man würdig
begehen, der Augenblick. Das ist jetzt der Bereich,
wo wir definitiv die Welt der relationalen Datenbank verlassen haben.
Denn das geht in relationalen Datenbanken nicht mehr.
Da müssen es Grundstrukturen sein. So, also,
wir nehmen diesen Fakt zur Hilfe. Und da,
der Funktor ist identisch, die Stelligkeit ist identisch,
beides mal zweistellig. Wir binden X an das Null an
die Null und das ist das erste Ergebnis,
was wir kommen
Weil nämlich auch die Unifikation auf der zweiten Argumentposition erfolgreich ist.
Also dieser S von S von 0,
Thermen unifiziert mit dem S von irgendetwas Therm hier,
in dem eine variablen Bindung hergestellt wird, die uns aber gar nicht interessiert.
Es ist nur, dass der positive Ausgang der Unifikation ist entscheidend.
Und das liefert uns das erste Ergebnis. Und das ist ein richtiges Ergebnis,
weil es nämlich sagt,
die Null ist kleiner als die 2. Wenn wir nach Alternativen fragen,
dann müssen wir die Regeln bemühen und die Regel sagt jetzt, ja,
dann unifiziere das X mit dem S von X1. Das ist die erste Wertebindung,
die hier hinten hergestellt wird,
die hier aufgeschrieben ist.
Und auf der zweiten Argumentposition das S von S von Null
mit S von Y1. Und dann wird hier auf der Position
das Innere dieses S-Ausdruckes herausgeholt und als Wert von Y1 verwandelt.
Also Y1 ist ein S von Null.
Hier sehen Sie zwei Sachen auf der gleichen Stelle.
Auf der ersten Position wird etwas konstruiert. Das sollte auffallen.
Wir bauen hier aus einem uninstanzierten Ding, machen wir jetzt,
ja, das Ding wird ein S von Etwas sein.
Hier bauen wir gerade eine Struktur auf. Und auf der
zweiten Argumentposition zerlegen wir sie. Wir stecken eine Struktur rein
und wollen ein Stück von ihr herausbekommen. Also,
die gleiche Implementation, Wir nehmen nur mit unterschiedlichen Instanzierungsvarianten hier uninstanziert,
deshalb Konstruktion einer Struktur und hier instanziert, deshalb Konsumption einer Struktur.
Gut, also, wir kriegen die beiden Bayerablennbindungen und mit denen
geht es in den Körper der Klausel.
Für X1 wissen wir noch nichts. Wir wissen nur,
was immer X1 sein wird. Es wird als Teil in
diese Struktur eingebaut werden. Aber für Y11 haben wir etwas,
das ist nämlich genau das um eins verringerte Ding von hier oben.
Und damit gehen wir jetzt in den Aufruf.
Wir haben wieder eine uninstanzierte Variable als erstes Argument und wir
haben einen vereinfachten PA-Nothermen auf der zweiten Argumentstelle
Und das modifizieren wir, wie wir mit dem Fakt der ersten Klausel,
die wir haben. X1 wird auf dem Wert null gesetzt,
das unifiziert und mehr wollen wir nicht wissen, ob es unifiziert.
Das heißt, wir kennen jetzt ein X1 und wir wissen,
dass S von X1 ein gewünschtes Ergebnis ist und das kriegen wir ausgegeben.
Die Antwort ist, eins ist auch eine PA-Notzahl,
die kleiner als zwei ist. Und wenn wir fragen,
gibt es weitere PA-Notzahlen, die die Bedingung erfüllen,
dann müssen wir wieder in die Regel gehen und
die Regel konstruiert jetzt auf der ersten Argumentstelle wieder etwas,
dass sie nämlich sagt, ja,
das X1 wird der Nachfolger von X-2 sein,
nur X-2 kenne ich noch nicht.
Und auf der ersten Argumentstelle wird, ja, sorry,
auf der zweiten Argumentstelle wird konsumiert.
Also dieses S von Null, was ich hier habe,
wird jetzt zu der vajablen Bindung Y2 ist gleich Null,
weil ich das Innere dieser Essstruktur haben will.
Das ist die Bindung, die hier hinten steht.
Und jetzt muss ich nachweisen, dass wann etwas immer das
hier ist, kleiner ist als die Null.
Das ist die neue Anfrage. Und das ist genau der Fall,
den wir vorhin diskutiert haben. Hier hinten haben wir jetzt
eine Null, der Fakt fordert aber eine Essstruktur.
Die Regel fordert auch eine Essstruktur, das heißt,
beide Klauseln scheitern
Es gibt kein weiteres Ergebnis. Wir versuchen noch,
ein Backtracking zum Entscheidungspunkt 2, aber sorry, beide Klauseln sind bereits abgearbeitet.
Das heißt, wir machen ein Backtracking zum Entscheidungspunkt 1.
Auch dort sind beide Klauseln bereits abgearbeitet.
Das heißt, weitere Ergebnisse gibt es nicht.
Wir haben die Menge der Ergebnisse vollständig aufgezählt. Die Frage,
welche Peanozahlen sind kleiner als 2, liefert dort zwei Ergebnisse,
nämlich die 0 und die 1. Was anderes kriegen wir nicht.
Das ist doch schon mal ganz erfreulich.
Das scheint also offensichtlich prozedural mit unserer Intuition recht gut übereinzustimmen.
Wir haben hier noch den gegenteiligen Fall, dass wir jetzt fragen,
welche PA-Anozahlen sind größer als die zwei?
Bisher haben wir nach Kleiner gefragt, jetzt fangen wir nach größer als die zwei.
Wir probieren das als erstes wieder mit dem Fakt.
Der Fakt führt nicht zu einer Ulifikation,
weil hier ein Atom steht, hier oben steht eine Struktur.
Das scheitert, also wir nehmen die Regel her,
die Regel konsumiert jetzt auf der ersten Stelle,
weil sie das Innere dieser PA-Notzahl haben will und sie konstruiert auf der zweiten Stelle,
weil sie eine Essschale außenrum rum baut.
Und mit diesen, dann ergeben sich die Wertebindung eben X1,
wird gleich S von Null sein und X ist S von Y 1,
aber das kennen wir noch nicht.
Damit gehen wir wieder in den Körper der Klausel.
Und auch hier wird jetzt wieder, Quatsch,
damit berufen den Körper der Klausel auf und nehmen als erstes den ersten Fakt.
Da scheitert die Unifikation mit dem Fakt wieder auf der ersten Argumentposition.
Wir nehmen die Regel zur Hilfe. Die Regel sagt,
konsumiere auf der ersten Position und gibt mir das Innere dieses S-Ausdruckes,
also X2 gleich 0 und baue hier eine Essschale um ein Y2,
was ich aber noch nicht kenne.
Aber wenn du es hast, dann ist es der Wert von Y1.
Und damit geht es wieder in den Körper der Klausel.
Das heißt, jetzt gibt es den Aufruf,
Die Null ist kleiner, ich brauche ein Y2,
sodass Y2 größer ist als die Null.
Das kommt ja zu dem Fakt und der Fakten sagt genau das,
ein Ding, was es von irgendetwas hat, ist größer als die Null.
Das heißt, jetzt weiß ich, Y2 gleich S von irgendetwas,
ist das Innere dieses S-Ausdruckes und dieser S-Ausdruck ist das Innere dieses S-Ausdruckes.
Das heißt, X ist gleich S von S von S
, von irgendetwas
Mit anderen Worten, dass hier ist jede PANO-Zahl von 3 auf aufwärts.
Das ist erstmalig ein unterspezifiziertes Ergebnis, was wir hier bekommen.
Mit diesem Ergebnis beschreiben wir unendlich viele PANO-Zahlen.
Denn hier drin ist im Ergebnis noch eine ungebundene Variable.
Die können wir instanzieren. Und wir können entweder hier innen drin eine Null reinschreiben,
dann ist es die 3. Oder wir können hier innen drin ein S von 0 reinschreiben,
dann ist es die 4. Oder wir schreiben ein S von S von 0 rein,
dann ist es die 5 und so weiter,
bis jetzt unendliche.
Das heißt, wir kriegen jetzt mit einem endlichen Ausdruck
hier beschreiben wir eine unendlich große Ergebnismenge.
Das ist der zweite Blick, den wir in die Tiefe
der Unendlichkeit tun. Und daran müssen wir uns jetzt gewöhnen,
wir reden ab sofort über Relation, über unendlichen Datenstrukturen.
Und dann sollten wir uns nicht wundern, dass wir Ergebnisse bekommen,
die über die Unendlichkeit sprechen. Das Schöne ist eben nur,
wir müssen die Dinge hier nicht alle aufzählen. Das heißt,
wenn wir ein bisschen Glück haben, vorhin hatten wir das Ergebnis,
wenn wir alle PA-Notzahlen aufzählen wollen,
dann müssen wir immer wieder sagen, ja,
gib mir noch eine, gib mir noch eine,
gib mir noch eine.
Hier kriegen wir ein einziges Ergebnis und dieses eine Dieses Zeug,
dieses eine Ergebnis, das umfasst unendlich viele Einzelsachen.
Oder beschreibt unendlich viele Einzelergebnisse. Aber sehe ich,
das ist richtig, dass dieses Resultat uns sagt,
wir können etwas Beliebiges an diese Position, das unser Strich setzt.
Richtig. Wir haben an keiner Stelle einen Typtest eingebaut.
Das werden wir eine Übung machen. Nehmen Sie die,
das ist genau eine der Aufgaben auf dem nächsten,
nein, mochte ich auch demnächst, auf dem übernächsten Übungszettel.
Was ändert sich, wenn ich jetzt in dieses,
in die Definition von kleiner als Typtests einbaue,
dass ich sage, das gilt nur, wenn beides PA-no-Zahlen sind,
dann ändert sich das Verhalten sofort.
Und das sollen Sie mal rauskriegen, wie und warum
Das sich ändert. Aber hier haben wir an keiner Stelle
darüber gesprochen, dass das wirklich saubere PANO-Zahlen sein müssen.
Dieses Ergebnis sagt eben, ich kann hier hinten beliebiges hineinunifizieren,
also was weiß ich, Hausschuhe oder Affe, das ist vollkommen egal.
Ich kann dort Zigarettenkippen hineinunifizieren oder irgendetwas. Es garantiert mir
nicht, dass das wirklich eine typ reine PANO-Zahl ist.
Das haben wir auch nirgendwo verlangt. Wir haben nur gesagt,
wenn du außen Essschalen abbauen kannst,
bist das Erste im Inneren angekommen ist.
Also das Erste muss tatsächlich eine PANO-Zahl sein.
Das sollte eigentlich deutlich werden. Auf der ersten Position
werden ja
PANO-Zahlen konstruiert
Beginnend eben mit der äußeren Essschale, bis irgendwann mal im Inneren,
ja, hier auf der ersten wird konsumiert.
Da ist eine Zwei da, im nächsten Aufruf habe ich
eine Eins da und im dritten Aufruf habe ich eine Null.
Hier wird konsumiert, bis ich bei der Null angekommen bin.
Das heißt, hier muss eine PA eine Zahl da sein.
Für die zweite Argumentposition besteht die Forderung nicht.
Da kann ich also Beliebiges hineinunifizieren. Hier hinten,
das ist eine vollkommen unrestrinkierte Variable, die dort steht. Gut,
also das Wesentliche, was wir hier sehen, ist,
wir haben Eine generische Beschreibung für eine unendlich große Resultatsmenge,
wo ich mit einem Ausdruck alle Ergebnisse erfassen kann.
Und das nennen wir ein unterspezifiziertes Resultat. Und das heißt,
was wir hier haben, ist wieder eine Sicht auf Programmierung,
als Informationsanreicherung oder nicht Programmierung, sondern Programmabarbeitung als Informationsanreicherung.
Ich stecke einen Aufruf in das System hinein und das System reichert
diesen Ausdruck so lange mit Informationen an, wie es kann.
Wie aufgrund der Definition, der Prädikatsdefinition für kleiner als,
die Information wird so weit wie möglich in diesen Ausdruck
hinein angereichert
Und das, was angereichert werden kann, ist, ja,
dann muss das X eben ein S von S von S von irgendetwas sein.
Mehr Informationen hat das Prädikat nicht. Es hat alles gegeben,
was es geben konnte und hat es hier sozusagen hineinunifiziert.
Auch da wieder Informationsanreicherung. Wir fragen eben unterspezifiziert mit dieser
uninstanzierten Vajablen und wir kriegen eine angereicherte Form dieses Ausdrucks.
Wo nehme ich hier hinten jetzt S von S von S
von irgendetwas steht? Das ist mehr Information. Wir kriegen
mehr Informationen raus, als wir da hingesteckt haben.
Aber die Informationen, die das System hat,
reicht nicht aus, um die Vajablen hier vollständig zu instanzieren
Bleibt hier im Inneren eine uninstanzierte Weihe habende. Damit müssen
wir leben. Gut, also das ist ein wesentliches Merkmal
der Logikprogrammierung, dass wir jetzt mitunterspezifizierten Daten arbeiten können.
Wir können unterspezifizierte Daten in die Berechnung hineinstecken und wir können
unterspezifizierte Daten aus der Berechnung wieder rausbekommen.
Und in einigen nicht immer, aber in einigen Fällen,
wenn es denn sich um rekursive Datenstrukturen handelt,
beschreibt dann ein solches unterspezifiziertes Resultat,
entweder eine große Menge von Ergebnissen oder gar eine unendlich große
Menge von Ergebnissen.
Das ist immer
das Charakteristikum seiner Unterspezifikation
So, nächstes Beispiel ist, wir wollen mal ein Prädikat schreiben,
was uns testet, ob denn eine PANO-Zahl gerade oder ungeratet zahlig ist.
Da gibt es natürlich wieder einen Streit darum, was ist die Null?
Ist das eine Gerade oder eine ungerade Zahl? Wir tun
mal einfach so, als ob uns das nicht interessiert.
Wir definieren die Null auch als Gerat zahlige PANO-Zahl.
Und wir nehmen eine Definition, die nicht,
das kann man auf verschiedene Arten und Weisen realisieren,
indem man also beispielsweise eine PA-Notzahl nimmt und immer zwei S-Schalen abmacht und je nachdem,
ob man bei der Null ankommt oder bei der S von Null,
ist es geratzahlig oder ungerat zahlig
Wir machen es hier anders, dass wir das immer schrittweise machen.
Also, wir wollen die Gratzeiligkeit auf die Ungeratzeiligkeit des Vorgängers
zurückführen und die Ungeratzeiligkeit des Vorgängers auf die Geratzeiligkeit dessen Vorgänger wieder zurückführen.
Das ist die Idee dabei. Sodass wir also zwei positive,
zwei Arten von Ergebnissen hier aufschreiben, es sind positive und negative dabei.
Einerseits für die Geratzeilige und andererseits für die Ungeratzeiligen.
Die Lösungsidee haben wir schon, wir wollen also das eine
auf das andere zurückführen und das wieder auf das zurückführen.
Also müssen wir uns immer die positiven Sachen rechts und links angucken,
also Evan von null und Ott von S von null
Und dann sehen wir wieder genau das Gleiche,
was wir vorhin auch gesehen haben, wenn wir das hier einfacher machen,
können wir diesen Fall auf den hier zurückführen.
Oder nächstes Beispiel, wir gucken uns, aha,
das ändert sich doch ein bisschen über der Zeit.
Wenn wir diese Ungeratsheiligkeit von eins nehmen,
dann können wir die Geratzeitigkeit von S von S von 0
nachweisen durch Bezug auf die Ungeratsheiligkeit von S von 0. Oder
die Ungratzahligkeit von der 3 können wir auf die Geratzahligkeit
der 2 zurückführen
Und damit haben wir die Definition wieder,
also der Fakt als allererstes, die Null ist eine gerade Zahl.
Eine Zahl ist geradzahlig, wenn denn ihr Vorgänger, eine Ungeratzahl ist.
Also wir nehmen von der, wir ziehen eins ab von
einer natürlichen Zahl und dann kriegen wir eine ungerade Zahl heraus.
Oder andersherum, wenn dieses P ungerade ist und wir addieren eins
drauf, dann ist es geradzahlig. Und umgekehrt genauso,
wenn dieses P geradzahlig ist und wir addieren eins drauf,
dann wird es ungerattzahlig
Was wir hier haben, ist erstmalig ein Fall von indirekter Rekursion.
Jetzt wird eine Prädikatsdefinition nicht mehr auf sich zurückgeführt,
sondern auf eine andere Prädikatsdefinition,
aber die greift dann weder auf die Ursprüngliche zurück.
Also wir haben hier jetzt einen Zyklus. Evan wird mithilfe
von Ott erklärt und Ott wird mithilfe von Evan erklärt.
Rekursion muss nicht immer über dem gleichen Prädikat passieren.
Es kann über ganze Ketten von Prozeduraufrufen passieren. Das ist
einer der Gründe, die zum Halteproblem der Turing-Maschine führt.
Ich kann solche Zyklen natürlich detektieren,
aber ich kann sie nicht detektieren,
wenn sie
unendlich groß werden
Das ist einer der Gründe, was uns zu den Gründen
der Berechnungs-, zu den Grenzen der Berechnungs-,
na nur nochmal, zu den Grenzen der Berechenbarkeit führt.
Dass solche Rekussionen eben über beliebig lange Zyklen passieren können.
Und deshalb gibt es keine allgemeine Lösung, die verhindern kann,
dass sich in solchen Fällen möglicherweise nicht terminiere. Also,
ja, das Problem ist ja nochmal beschrieben oder die Struktur ist beschrieben,
der rekrusive Aufruf ist nicht unmittelbar in der Klausel,
in der das Prädikat definiert wird,
sondern in einem zweiten oder dritten oder vierten Prädikat,
Direkt oder indirekt von Ersterem aufgerufen werden.
Also diese indirekte Rekursion kann dann auch über mehr als eine
Zwischenstufe erfolgen. Dann schauen wir uns doch mal an,
wie das Ganze tatsächlich, ach so, nein,
das schauen wir uns gar nicht an.
Das nächste Beispiel und jetzt wird es langsam spannend.
Wir können es ja schon mal einführen.
Auch wenn wir zu den Details nicht kommen. Bis jetzt
haben wir ja noch gar nicht gerechnet. Wir haben bisher
einzig und allein Tests durchgeführt. Ein Test auf Typ,
bist du eine PA-Notzahl oder nicht? Ein Test auf Größenvergleiche,
so kleiner als die andere PA-No-Zahl oder einen Test auf Geratzahligkeit, Ungeratzahligkeit.
Jetzt wollen wir erstmalig rechnen. Und die einfachste Rechenoperation,
die wir über den natürlichen Zahlen haben, ist die Addition.
Und wir wollen also die Addition als rekursive Prozedur über einer
rekursiven Datenstruktur, also dem PA-No-Thermen, uns anschauen.
Und auch hier wieder das Vorgehen, wir haben negative Testfälle,
die stehen hier hinten und wir haben positive Testfälle,
die stehen hier vorne.
Und wir gucken uns wieder die positiven Testfälle durch die Brille
unserer Unifikationsoperation an und schauen,
ob wir da Gemeinsamkeiten finden
Ja, und wir schauen uns die ersten beiden Zeilen hier
an und was sind die Gemeinsamkeiten, die wir sehen?
Das zweite und dritte, man ist bei beiden gleich.
Genau, also hier vorn kommt eine Null hin,
wenn hier hinten Gleichheit. Oder sagen wir allgemeiner Unifizierbarkeit besteht
. Aber das läuft in diesem Fall auf Gleichheit hinaus.
Und genau das ist die erste Zeile unserer Definition.
Also die Null mit irgendetwas addiert, ist dieses irgendetwas.
Das ist die typische Akzeomatisierung oder mit anderen Worten,
die Null ist das neutrale Element der Addition.
Ich kann auf alles eine Müll drauf addieren und das ändert
sich nicht
So, und dann müssen wir weitergucken nach weiteren Gemeinsamkeiten,
die wir hier finden.
Und zu diesem Zweck sollte man sich wieder eine Metapher bebühren.
Was heißt Gleichheit? Wir reden hier im Wesentlichen über Gleichheit
von einer unterschiedlichen Zahl oder von unterschiedlichen numerisch unterschiedlich großen Objekten.
Und für die Gleichheit ist eigentlich eine schöne Metapher,
die man immer verwenden kann, die Metapher der Waage.
Ich habe eine Waage, ich für zwei Schalen dran.
Ich habe auf der einen Seite zwei Kästen und
ich habe auf der anderen Seite
einen Kasten
Und die Waage muss im Gleichgewicht sein. Das heißt,
die Anzahl der Kugeln, die ich hier drin habe,
muss gleich der Anzahl der Kugeln,
die hier drin sind, sein.
Und jetzt kann ich hingehen und kann mir überlegen,
welche Operationen kann ich machen, an dieser Belegung der Variablen,
ohne dass sich das Gleichgewicht gestört wird.
Denn die Addition setzt natürlich immer wieder voraus,
dass das die Summe dieser beiden Dinge hier muss gleich dem sein,
was hier drin ist.
Und das kann man jetzt auf verschiedene Arten und Weisen machen.
Ich kann beispielsweise hier eine zusätzliche Kugel hineinlegen.
Da muss ich hier irgendwo eine zusätzliche Kugel hineinlegen.
Entweder hier oder da,
es ist egal wo
Aber ich muss mich irgendwann einigen,
ob ich es hier mache oder ob ich es da mache.
Es gibt zwei völlig äquivalente Definitionen, die sich
nur dadurch unterscheiden, dass sich die beiden Argumente austausche.
Also auf eins muss ich mich einigen,
du musst also beispielsweise, irgendwie habe ich es hier gemacht,
ich habe es hier beim ersten gemacht.
Also wir einigen uns, dieses Hinzufügen oder wegnehmen,
passiert immer auf dem ersten Kasten. Also, wenn ich
dieses Gleichgewicht hier nicht stören will und hier eine Kugel entferne,
das geht natürlich fürs Hinzufügen, wie fürs Entfernen.
Wir machen hier die Addition in der Übung, kommt die Subtraktion dran.
Also das ist kein Unterschied. Wenn ich hier eine entferne,
muss ich hier auch eine entfernen, dann ist das Gleichgewicht wieder und bewahrt.
Und genau das ist hier unten in dieser Gleichung oder in dieser Vorschrift aufgeschrieben.
Wenn ich denn aus der ersten Kiste eine Kugel wegnehme oder
der Matroschka eine Schale raube oder der Peanu-Zahl eine Essschale wegnehme,
dann muss ich es auch auf dem Ergebnis machen.
Also sorry, ich habe dich dazu gesagt. Erstes Argument ist
der erste, so mein zweiter, so meint die Summe.
Wir reden über relationale Arrithmetik, relationale Arrithmetik heißt ja,
wir haben keinen Funktionswert zur Verfügung, um das Ergebnis mitzuteilen.
Relationale Beschreibungen entstehen aus funktionalen, typischerweise immer dadurch,
dass sie ein zusätzliche Argumentstelle brauche,
über die ich das Ergebnis konsumiere
Also wenn wir Additionen in der funktionalen Welt als zweistellige Operationen haben,
dann brauche ich ja eine dritte Argumentstelle,
wo ich das Ergebnis übergeben kann. Und die beiden,
die Sachen sind eben genau in dieser relationalen Sicht hier verankert.
Das sind die beiden Summanden und hier ist die Summe.
Und ich brauche all drei in meiner, in meiner Relation.
Wenn ich, wenn ich das mit dem addiere,
kommt das raus. Das ist genau diese Gleichheit,
dieser ausbalancierte Zustand der Waage, den ich erreichen will.
Also ich nehme von der ersten Schale etwas weg,
das ist ganz links die.
Die zweite lasse ich unberührt, das ist diese hier.
Und dann muss ich aus der dritten Schale auch etwas wegnehmen.
Dann ist das Gleichgewicht
wieder gewahrt
Das ist die Definition der Addition. Mehr steckt da nicht
dahinter. Und das können wir uns hier an diesen Testfällen
auch wieder sehr schön veranschaulichen.
Wenn ich also hier etwas wegnehme und hier etwas wegnehme,
kann ich diesen Fall auf den Fall hier zurückführen.
Das klappt hier nicht. Hier kann ich nichts wegnehmen.
Oder wenn ich hier etwas wegnehme und da etwas wegnehme,
kann ich den vierten Fall auf den dritten Fall zurückführen.
Auf die Art und Weise kriege ich also die Addition in den Griff. Gut,
und das schauen wir uns dann nächsten Freitag an,
wie das jetzt hinter den Kulissen tatsächlich abläuft,
also wie die Ergebnisse sukzessive rational auf der Ebene der virtuellen Maschine gebildet werden.
Das ist erstmal die äußern sich,
die deklarative Sicht und bis dahin ein schönes Wochenende und tschüss