So, einen schönen guten Tag. Wir hatten uns ja
letzte Woche damit befasst, dass wir PANO zahlen,
als einfachste rekursive Datenstruktur hier eingeführt haben,
um an diesem Anwendungsfall eine möglichst einfache Einführung in die Benutzung rekursiver Datenstrukturen zu haben.
Wir hatten verschiedene Prädikate uns angeguckt,
die man mit auf dieser Basis definieren kann.
Nein, das war falsch,
da wollte ich hin
Im einfachsten Falle also ein Typtest,
in dem wir mal geguckt haben, ob wir,
wie man ermitteln kann, ob eine PA-Notzahl vorliegt oder nicht.
Wir hatten in diesem Falle gesehen, dass die Beobachtung,
die wir bereits bei den relationalen Datenbanken gemacht hatten, hier wieder gilt,
dass wir nämlich in unterschiedlichen Instanzierungsvarianten arbeiten können und auf die Art und Weise unterschiedliches Verhalten hervorrufen können.
Zum Beispiel, in diesem Falle sind nicht allzu viele Varianten da.
Wir können das gleiche Programm uninstanziiert verwenden und uns Beano-Zahlen generieren lassen.
Wir können das Programm aber auch konsuiert verwenden,
um uns PA-No-Zahlen daraufhin untersuchen zu lassen,
ob sie denn den Typ erfüllen oder nicht.
Das sind die beiden Varianten, die wir hatten,
ein etwas anspruchsvolleres Ding war, der Größenvergleich, den wir hier haben,
wo wir also zwei PA-No-Zahlen in Relation zueinandersetzen können und gucken können,
ob die eine kleiner ist als die andere.
Wir haben beide Varianten getestet,
insbesondere haben wir die letzte Variante auch dahingehend getestet,
dass wir zum Beispiel mal gefragt haben,
was ist denn alles größer als die zwei
Und da hatten wir die interessante Beobachtung gemacht,
dass jetzt in diesem Falle wir mit unterspezifizierten Ergebnissen rechnen können,
die in dieser Form hier uns sagen, ja,
alle PANO-Zahlen, die mindestens drei solche Essschalen außen drumherum haben
und über deren Inneres ich nicht viel mehr sagen kann,
das sind mögliche Ergebnisse für diese Anfrage.
Das heißt, wir haben hier wieder den Fall der Informationsanreicherung.
Wir gehen mit einer unterspezifizierten Aufruf in die Suche hinein und das Prädikat,
kleiner Alt, fügt so viel Information,
es zur Verfügung hat,
zu dieser Anfrage hinzu
Und kommt dann mit dieser unterspezifizierten Antwort zurück. Die Informationen,
die hinzugefügt wurde, die sehen wir hier,
das ist nämlich genau die Informationen, die sich aus dem Rekursionsschritt ergibt,
dass nämlich auf beiden Stellen eine Essschale hinzugefügt wird.
Und das ist genau passiert, dass nämlich zu der Zwei,
die wir eingegeben haben, eine Essschale draufgebaut wurde,
weil wir das entsprechende Argument unterspezifiziert aufgerufen haben und damit,
nein Quatsch, ist, durch den Abschluss hinzugekommen.
In dem Augenblick, wo wir hier die Null haben,
wird hier noch eine Essschale drumherum gebaut.
Und diese Uninstanzierte Variable, weil hier drin eine uninstanzierte Variable steckt,
dann kommt nach außen hin ein Ergebnis, was auch unterspezifiziert ist.
So, wir haben an diesem Fall also,
ach so, heute ist es mir endlich gelungen,
bei dem Anschauungsmaterial hier auch tatsächlich mitzubringen.
Wir hatten ja die Analogie zur Matroschka bemüht, die Frage,
also wie kann man elementare Eigenschaften von solchen Puppen ermitteln,
also etwa die Frage, wie tief sind sie ineinander eingebettet?
Oder aber auch die Frage, welche enthält denn mehr
Solche Puppen als die andere. Und wir wissen eben,
dass der äußere Größenvergleich da sehr trügerisch sein kann,
da die Dinge in vollkommen anders gebaut sind.
Kann ich also von außen nie beurteilen,
welche dieser Peanu-Zahlen ist nun größer als die andere.
Wir haben jetzt hier verschiedene Tests durchgeführt. Wir hatten das
eine mit einer Variablen, also erstes Argument mit einem Variablen,
zweites Element mit einer Variablen.
Und wir haben dieses unterspezifizierte Resultat bekommen. Was wir noch
überhaupt nicht gemacht haben, und das hat bisher auch keiner eingeklagt,
sind weitere Testfälle, die wir hier noch nicht berücksichtigt haben.
Was könnte man
noch alles machen
Und ich möchte jetzt, dass alle darüber nachdenken,
nicht nur einer. Was haben wir bisher überhaupt noch nie
angefasst? Also wir können es instanziert aufrufen,
wir können das uninstanziert im ersten Argument aufrufen,
wir können uns das am lebenden Objekt angucken,
werden wir also kleiner als so etwas hatten wir.
Ja, ups, es ist Nano von S von Null.
Und wir wollen wissen, welche Pianozahlen sind.
Stehen in der kleinen Alz-Relation, dann kriegen wir dieses Ergebnis,
was wir eben gesehen haben.
Oder wir machen es eben umgekehrt
Wir fragen, was ist kleiner als die zwei und kriegen die Aufzählung,
das ist die Null und das ist die Eins.
So, ist damit der Raum von Möglichkeiten erschöpft,
was wir mit dem Prädikat machen können?
Ja, haben wir gut. Das ist ja auch vollständig
, ich bin ja spezifiziert. Genau, das können wir
auch machen. Also wir können vollständig, richtig,
der Einwurf ist völlig berechtigt, das haben wir noch nicht gehabt.
Also die Null ist kleiner als es gefolgt von S um
irgendeine uninstanzierte Variable drumherum oder aber die Eins ist kleiner als S von S von irgendetwas.
Wir kriegen dann also alle Paare aufgezählt,
die wir auch hier oben uns hätten denken können.
Gut, bis damit der Raum der Möglichkeiten immer noch nicht erfüllt.
Was fehlt noch? Wenn wir ein unterspezifiziertes Ergebnis,
ein unterspezifizierten Therm als Ergebnis bekommen,
dann sollten wir an der Stelle mutig werden und fragen,
was passiert eigentlich, wenn wir einen unterspezifizierten Therm als Input abverwenden.
Das muss man doch mal gucken. Also wir fangen mal,
ich hoffe, dass Piano hier drin ist.
Ich habe jetzt einfach irgendeinen Pfeil genommen. Ich hoffe,
dass die Definition dabei ist. Also wir fragen jetzt mal,
Ist denn S von X eine PA-No-Zahl?
Meinst du, was wohl rauskommt? Und jetzt bitte ich
jeden mal einen Zettel zu nehmen,
jedem mal einen Zettel zu nehmen und ihre Hypothese aufzuschreiben, was glauben
Sie, was als Reaktion darauf passiert
Musik So, dann fangen wir mal an. Was ist die erste Hypothese?
Was kommt wohl raus? Ja. Ich würde sagen,
da kommt nichts gleich null und dann kann man das weitermachen
mit Flight S9, SS9. Genau,
das sollte also das sein,
was tatsächlich passiert
Absolut. Ja, es ist, es ist,
die Definition ist tatsächlich nicht drin in der Datei.
Tut mir herzlich leid. Dann müssen wir sie eben hinzufügen
. Das ist, ich habe es vorher nicht getestet,
ich hätte es mal ausprobieren sollen.
Habe ich nicht gemacht. Also PA, nur von null Piano.
Ups. Das von X. Scheinen sie auf, wenn ich
was Falsches schreibe. So, jetzt sollten wir vielleicht mehr
Glück haben. Okay, erster Teil stimmt schon mal.
Wir kriegen also mit dem Fakt, den wir drin haben,
die Null ist eine PA-Notzahl, das erste Ergebnis.
Wenn wir denn das X durch Null substituieren, dann ist es eine PA-Notzahl.
Wenn wir denn das X durch S von Null substituieren,
ist es eine PA-Notzahl und so weiter und so fort.
So, damit sind wir mutig geworden. Jetzt können wir
das Ganze noch mal probieren mit dem kleiner Alt.
Haben Sie mal einen Aufruf, runterspezifizierte Therme an dem Argument stellen?
Ja. Also ganz ehrlich, esste von links, 1,1 von links.
Das ist noch relativ simpel. Also die Antwort lautet,
ja, die Null, das ist im Prinzip nichts anderes
als die Modifikation dessen,
was wir vorhin hatten
Na, kriege ich das hier noch? Nein, kriege ich nicht mehr.
Doch, kriege ich noch. Was wir vorhin hatten,
als wir das vollständig unterspezifiziert aufgerufen haben. Jetzt rufen wir es,
um diese vajablen drumherum jeweils noch mit einer Essschale auf.
Und dann sollt ihr nicht sehr verwunderlich,
solltet es sich sehr verwundern, wenn wir das Gleiche,
ich mache es nochmal, richtig, das gleiche Verhalten kriegen,
also die gleichen Antwortmenge bekommen, weil ja unser Verfahren nichts anderes macht,
als in jedem Schritt eine Essehülle wegzunehmen und sich für das Innere zu interessieren.
Und damit hat es diesen Aufruf zurückgeführt,
auf kleiner als X,
Da ist, das ist nicht sonderlich spannend.
Was wir aber tatsächlich machen können, ist,
dass wir das jetzt mal instanzieren hier. Ja,
also in diesem Fall ist es auch noch nicht so,
eins, was jetzt rauskommt.
Das ist noch nicht die Krönung des Ganzen.
Wir kriegen die Antwort mit Y, sodass das Y
größer ist als die eins.
Wollen wir es probieren? Das ist genau das, was passiert.
Y muss größer als eins sein, das heißt also zwei und aufwärts.
Hier hinein kann ich eben wieder jede beliebige PA-Nutzahl schreiben.
So, und jetzt machen wir das Spiel,
drehen wir das Spiel um und fragen, was ist wohl?
Ja, welche bayerblen Subsituationen für X kann ich verwenden,
damit diese Gleichheitsredaktion gegeben ist
Okay, warum? Die beste Zeit der Jude 1,
das ist keiner als Geister. Und Alternativen? Ich weiß
nicht, negativ sein. Wir reden über natürliche Zahlen.
PR-Notzahlen sind natürliche Zahlen da. Da sollte es keine Negativen geben.
Also sollten wir erwarten, dass da nichts weiterkommt. Gut,
wir müssen uns mal auf der Zunge zergehen lassen, was wir hier machen.
Wir stellen also jetzt Anfragen der Art, wie in diesem Falle hier,
gib mir alle Zahlen, die Größer sind als zwei.
Ach Gott, wie kann man denn das paraphrasiert halten?
Das ist ja... Mit dieser, ein Aufruf dieser Art,
ist nichts anderes als die Frage an das System,
was muss sich in das Innere einer PA-Notzahl alles hineinschieben,
also in das Innere einer solchen Matroschka hineinschieben,
damit eine bestimmte Bedingung erfüllt wird.
Und in diesem Fall ist es die Bedingung eben,
dass das, was dabei entsteht,
kleiner sein soll,
ein größer sein soll als die zwei
Das ist das, was diese Anfrage fordert. Das heißt,
wir können jetzt hier auch im Input über beliebig große Zahlen reden.
Dieser Ausdruck ist von Y, steht ja für unendlich viele Zahlen.
Wir können Anfragen stellen, nicht nur als Ergebnisunterspezielle Antworten bekommen,
sondern wir können auch Anfragen stellen, wo wir über unendlich viele Zahlen reden.
Das ist die Quintessenz des Ganzen hier. Und dieses Spiel können Sie mal,
deshalb mache ich das Ganze hier durchaus auch im Rahmen der Übung,
sich nochmal vergegenwärtigen, was eigentlich mit den ganzen Prädikaten passiert,
die wir da aufstellen.
Ob die tatsächlich in dieser Hinsicht wasserdicht sind,
dass sie also nicht nur einem Output unter Spezifikation bekomme,
sondern dass ich auch im Input über Unendlich viele natürliche Zahlen reden kann.
Ja. Mit dieser Definition ist natürlich immer noch zu gewinnen,
dass wir jetzt fragen können, LT von 0,30. Ja.
Ja? Warst du irgendwas,
ob das wir trotzdem Tour rausbekommen und der wird dann in Konzernen einsetzen,
denn in der Definition der Demorie, die anonyme Variable verwendet,
das heißt, da könnte man noch etwas einsetzen,
was gar keine übliche Fährungszahl ist.
Ja, solange wir keinen Typtest machen,
auch das ist Gegenstand der Übung,
solange wir keinen Typtest machen,
kann ich da Believious in einen unifizieren
Das sind immer im Output auch uninstanzierte Vajablen, die keinerlei Typbeschränkungen kennen.
Da kann hineinunifiziert werden, was immer man hineinunifizieren will.
Wenn ich das anders haben will, muss ich
einen Typtest einbauen. Den haben wir hier nicht eingebaut,
sondern wir haben einfach nur aufgrund des Äußeren Erscheinungsbildes gesagt,
wann immer wir an dem Falle kommen,
dass wir das Entelement bei dem einen erreicht haben,
dann können wir davon ausgehen, dass das andere eine gültige Pianozahl ist.
Das muss natürlich nicht der Fall sein,
das kann man verletzen
Gut, also, die wesentliche Botschaft ist,
Unterspezifikation von Ausdrücken erlaubt es uns über unendlich viele Instanzierungen zu reden.
In diesem Falle also über unendlich viele PA-No-Zahlen,
die alle die Bedingungen erfüllen, dass sie eins oder größer
sind oder in einem anderen Falle eben zwei oder größer sind.
Und dies kann also sowohl im Output auftauchen,
dass das System uns darüber mit einem Ausdruck informiert,
es ist eine unendlich große Ergebnismenge oder aber es kann auch im Input passieren,
dass wir solche Ausdrücke im Input verwenden und damit Anfragen stellen über alle Elemente einer unendlich großen Datenmenge.
Und das ist tatsächlich ein Effekt,
den man in keinem anderen Paradigma findet,
dass man sowohl im Output als auch im Input über unendlich große Ergebnismengen oder überhaupt Daten,
auch Mengen von Datenobjekten reden kann.
Und diese Idee der Unterspezifikation,
die ist also durchgehend und die ist auch für das Verstehen der Logikprogrammierung wichtig,
auch für das Prozedurale Verstehen,
weil nämlich die Informationsanreicherung in vielen Fällen immer so funktioniert,
dass sich das Äußere der Datenstruktur
bereits kenne
Aber da innen drin lungern noch irgendwelche Uninstanzierten, Variablen.
Und ich mache den rekursiven Aufruf nur, um diese Umterspezifikation weiter einzuschränken.
Um diese Variable, die dort noch drin steckt, zu binden.
Dann kann der Term, den ich da hineinfüge,
natürlich wieder eine Variable enthalten. Und ich muss den nächsten Rekursionsschritt machen,
um dort wieder ein Ergebnis hineinzubekommen. Und erst,
wenn dieser Mechanismus klar ist,
dass sich dort nicht eine Datenstruktur von innen nach außen aufbaue,
indem ich immer außenweite Schalen drum fülle,
sondern dass ich sie von außen nach innen aufbaue,
dann erst wird der Unterschied zur funktionalen Programmierung deutlich
Solange sie das nicht eingesehen haben, sieht das alles wie funktionale Programmierung aus.
Ist es aber nicht. Ich bin in der Lage,
hier rekursive Datenstrukturen von außen nach innen aufzubauen und nicht von innen nach außen.
Und dadurch, das wird die Quintessenz der Gesamten,
der gesamten Vorlesung sein, bin ich in der Logikprogrammierung in der Lage,
endrekursive Prädikate zu schreiben, an Stellen,
wo es in der funktionalen Programmierung nicht geht,
und zwar prinzipiell nicht.
Das ist also ein Mehrwert, den ich bekomme,
dank der Unifikation, weil die Unifikation ist mir eben
erlaubt,
mit unterspezifizierten Datenstrukturen umzugehen
Das nur als kleiner Vorausschau und jetzt kommen wir tatsächlich wieder zum Gang der Dinge.
Wir sind dabei abgebrochen, dass wir die Addition eingeführt haben.
Die Addition basiert auf der Metapher der Waage.
Wir haben ja eine relationale Sicht auf Addition. Das heißt,
wir wollen nicht den Funktionswert ermitteln,
der sich für die Addition zweier Zahlen ergibt,
sondern wir wollen die Relation ermitteln,
die zwischen den zwei Zahlen,
die ich addiere und der Summe besteht
Es ist also eine dreistellige Relation jetzt,
die zwei Input-Sumanten hat und eine Output-Summe hat. Und natürlich,
da es eine Relation ist, werden wir erwarten,
dass wir das Ganze auch unterspezifiziert aufrufen können und dann zerlegen,
eine Zahl in ihre Resumanten beispielsweise zu zerlegen können.
Aber das müssen wir testen. Das Bild der Waage hatte ich
als letztes diskutiert vor die Woche. Das besteht also darin,
dass ich die Waage habe, die im Gleichgewicht sein muss.
Auf der einen Seite sind zwei Kästen,
auf der anderen Seite ist ein Kasten und wann immer ich hier eine Kugel hineinlege,
Dann muss auch hier eine Kugel hineingelegt werden.
Und wenn ich hier und da eine Kugel hineinlege,
dann muss ich das Gleichgewicht herstellen, indem ich auch hier zwei Kugeln hineinlege.
Das ist die Idee. Und jetzt muss ich mich entscheiden,
an welcher Stelle möchte ich das hineinlegen und entfernen von Kugeln machen.
Und das ist in diesem Falle die erste Argumentstelle.
Und damit ist die Prädikatsdefinition hier unten auch klar.
Wir entfernen aus der ersten Kiste hier vorn eine Kugel.
Dann müssen wir auch aus der zweiten Kiste hier hinten
eine Kugel entfernen,
um das Gleichgewicht nicht zu stören
Also wenn wir hier eine entfernen, dann müssen wir auch hier eine entfernen.
Oder umgekehrt, wenn wir hier eine drauflegen, müssen wir auch hier eine drauflegen.
Das ist die Berechnungsvorschrift. Und damit führen wir den komplexen Fall,
dass wir also hier noch googeln, entfernen können,
auf den einfachen Fall zurück.
Und da wir hier die Kugeln entfernen,
machen wir das Ganze so lange, bis wir bei der Null angekommen sind.
Und dann, wenn hier keine Kugeln mehr drin ist,
dann muss die Anzahl der Kugeln,
die hier drin sind und die hier drin sind,
gleich groß sein.
Auch dann ist das Gleichgewicht wieder gegeben. Wir könnten das
Ganze auch wieder aus den Fakten hier ableiten können.
Also Fakten sind etwa die positiven Fakten
Wenn ich die Null mit der Null addiere,
kommt null raus, wenn ich null mit 1 addiere.
Eins, wenn ich eins mit null addiere, eins und
wenn ich zwei mit eins addiere, kommt drei heraus.
Und da muss man wieder gucken,
was sind gemeinsame Fälle, die man heraus generalisieren kann.
Das kann man natürlich jetzt auf ganz viele verschiedene Arten und Weisen machen.
Ich habe die Testbeispiele schon so ein bisschen hingeschrieben,
dass die Definition hier unten dann tatsächlich herauskommt.
Also aufgrund der Erfahrung, die wir mit den bisherigen Sachen hatten,
also kleiner Alz und Piano und so weiter,
ist es immer sinnvoll, die Fälle heraus zu generalisieren,
die hier eine gemeinsame Konstante drin haben.
Weil das nämlich auf den Rekursionsabschluss hinausläuft
Also können wir sagen, wann immer hier vorn eine Null ist,
dann muss das gleich dem sein und dann muss das gleich dem sein.
Und das können wir auch für 0 plus 2 gleich 2,
0 plus 3 gleich 3 und so weiter nachvollziehen.
Also dass das tatsächlich eine valide Hypothese ist.
Und das ist genau der Rekursionsabschluss, der hier steht.
Wir hätten es natürlich auch anders machen können.
Wir hätten gucken können, wenn hier eine Null ist,
an der zweiten Argumentposition und einer zweiten Argumentposition hier auch,
dann muss die erste gleich der dritten sein.
Und das sehen wir hier unten auch. Die erste muss
gleich der dritten sein. Und dann hätten wir die andere Variante bekommen,
wo wir nicht hier in dieser Kiste reinlegen und rausnehmen,
sondern wo wir das bei der Kiste machen
Die ist volkständig symmetrisch dazu. Die Addition ist symmetrisch,
insofern brauchen wir uns da auch nicht zu wundern, dass das herauskommt.
Und jetzt müssen wir als nächsten Schritt wieder schauen,
wie können wir die komplexen Fälle auf die einfachen Fälle reduzieren.
Und das müssen wir wieder durch die Brille unserer Unifikation machen.
Also wir können Strukturen aufbauen, wir können Strukturen zerlegen. Wenn wir also,
hier wollen wir zerlegen, weil wir es auf einen einfacheren Fall zurückführen wollen,
wenn wir also hier eine Essschale wegnehmen und hier eine Essschale wegnehmen,
dann haben wir es auf diesen Fall reduziert.
Und wenn wir hier eine Essschale wegnehmen und hier eine Essschale wegnehmen,
dann haben wir es auf keinen der Fälle hier reduziert,
der ist nämlich nicht dabei,
dann hätten wir es auf den Fall reduziert, S von 0,
S von 0 und S von S von 0 und da müssen wir das Gleiche nochmal anwenden und dann können wir es auf diesem Fall hier reduzieren.
Und das ist der Rekussionsschritt, der hier unten steht,
nimm hier eins weg, nimm da einen Zweck, dann ist das Gleichgewicht wieder gewahrt.
So, also auf in die Tests,
wir schauen uns wieder nur die unterspezifizierten Aufrufe an,
die spezifizierten haben wir ja für den Aufbau der Prädikatsdefinition eben verwendet Also,
was passiert, wenn ich die kanonische Verwendungsweise,
ich habe zwei Resumanten gegeben und ich möchte die Summe wissen,
kanonische Verwendungsweise heißt hier in diesem Falle 2 plus 1,
was ist das?
Ich nehme den Fakt, der Fakt scheitert, weil hier eine Konstante,
die null steht und hier ein komplexer Term, das unifiziert nicht.
Ich muss den Rekulsionsschritt bemühen,
der nimmt mir das Innere dieser 2 heraus,
also ein S von 0, das wird ein X1 gebunden,
das steht dort.
Und das Y1 wird an die zweite Argumentposition,
also S von 0 gebunden,
da ist nichts
an Berechnung passiert
Und hier hinten unifiziert jetzt das S von R1,
mit der Summe hier oben. Das heißt, hier sehen
wir wieder, wie eine Struktur von außen aufgebaut wird.
Zu diesem Zeitpunkt weiß ich bereits, wie die Summe heißen wird.
Ich kenne nur das Innere noch nicht. Und damit ich
das Innere kennenlerne, rufe ich das Ganze rekrusiv auf.
Nämlich mit dieser vajablen Belegung im Körper der Klausel.
Und das führt zu dem Aufruf,
Adieremir 1 mit 1 und gib mir das Ergebnis.
Der Fakt scheitert wieder, weil hier immer noch ein komplexer
Term dasteht und hier aber eine Null gefordert wird.
Also wieder die Regel angewendet.
Die Regel nimmt hier
eine Essschale weg
Bindet X2 also an Null, nimmt hier hinten eine Essschale weg,
bindet er 2 an X2 und übernimmt, sorry,
bindet R1 an S von R2.
Und auf der zweiten Argumentstelle, ich habe in die falsche
Zeile geguckt, wird Y2 an S von Null gebunden.
Das sind die vajablen Bindungen, die hier stehen.
Und auch jetzt weiß ich bereits wieder, die Summe wird S
von R1 sein und R1 ist S von R2. Das heißt,
ich weiß jetzt bereits, dass die Summe Äh, S,
von S, von R2S. Alles, was ich nicht kenne,
ist R2 und um es zu berechnen,
rufe ich diesen Körper der Klausel auf.
Jetzt habe ich also die Aufgabe, 0 plus 1 soll irgendetwas sein.
Da ist der Fakt einschlägig, weil jetzt die Unifikation auf
der ersten Stelle gelingt. X3 wird an S von 0
gebunden und das wird per Co.-Referenz als Wert von X2 etabliert. Das heißt,
jetzt weiß ich, dass R2 gleich S von 0 ist,
dass es diese bariablen Bindung, dann weiß ich auch,
dass R1S von S von 0 ist und da ich weiß auch,
dass die Summe gleich S von S,
von S von 0 ist,
also drei
Und das ist das Ergebnis, was ausgegeben wird,
wenn ich nach Alternativen frage, komme ich wieder zu der Regel.
Die Regel fordert hier vorne einen komplexen Term.
Hier steht aber eine konstante, ein Atom, eine Zahl.
Und deshalb scheitert die Unifikation mit dem Kopf der Regel.
Ich mache Backtracking zum Entscheidungspunkt 2. Da habe ich aber bereits
beide Klauseln abgearbeitet. Im Entscheidungspunkt 1 ist genau das Gleiche passiert.
Beide Klauseln sind bereits abgearbeitet und deshalb gibt es kein weiteres Ergebnis.
Das Ganze jetzt symmetrisch dazu mit dem zweiten Ah,
nein, nicht symmetrisch dazu, sorry.
Das Ganze aufgerufen mit dem einem somannten Unterspezifiziert,
der erste Aufruf war ja somant, plus omant gegeben.
Die Summe sollte gesucht sein. Hier gebe ich jetzt
die Summe vor und ich suche den somannten,
den ich auf die zwei drauf addieren muss, damit die drei dabei entsteht.
Auch hier wieder der erste Aufruf scheitert mit dem Fakt,
weil hier konstante auf Struktur trifft, konstante ist falsch,
weil hier Zahl auf Struktur trifft.
Ich muss die Regeln verwenden,
die Regel gibt mir das Innere von dieser zwei,
als den Wert von X1,
also erst von
Es stellt eine Cho-Referenz zwischen,
nein, zwischen dem Sumanten und diesem Y1 her.
Das ist diese Cho-Referenz. Das heißt,
was immer mal der Wert von Y1 sein wird,
es wird auch der Wert des Sumanten sein.
Nur kenne ich ihn noch nicht. Und auf der dritten
Argumentstelle wird wieder das Innere dieser Drei herausunifiziert.
Also R1 wird ein S von S von Null gebunden.
Mit diesen Werten geht es in den Körper der Klausel.
Der Aufruf heißt jetzt also, addiere mir eins mit irgendetwas,
sodass zwei herauskommt und sagt mir
den Wert von irgendetwas
Und der Fakt scheitert wieder aus den altbekannten Gründen.
Die Regel baut hier vorn eine Essschale ab und hier hinten
eine Essschale ab, sodass der neue Aufruf lautet,
addiere mir die Null mit irgendetwas, so das Eins herauskommt.
Und für diesen Aufruf sagt mir der Fakt,
was das Ergebnis ist, weil er nämlich wieder,
wenn hier vorne eine Null ist,
die Choreferenz zwischen zweiter und dritter Argumentstelle herstellt.
Und das heißt Y2 ist es von Null. Da ich weiß,
Y2 ist mal mit Y1 per Choreferenz verbunden worden und
Y1 ist gleich dem Semanten kenne ich zu diesem Zeitpunkt
das gewünschte Ergebnis,
der Somant
es gleich einschätzt
Den ich auf die 2 drauf addieren muss, damit ich insgesamt eine 3 erhalte.
Auch hier wieder Fragen der Alternativen. Die Regel bringt nichts,
weil die Null nicht mit S von S irgendetwas unifiziert,
alle Entscheidungspunkte sind vollständig abgearbeitet, weitere Ergebnisse gibt es nicht.
Ja, ich kann das Spiel jetzt weiter treiben,
indem ich das symmetrisch mache. Ich habe jetzt keinen Tres mehr aufgeführt,
sondern nur noch die Ergebnisse. Was muss ich,
welche Zahlen muss ich mit 1 addieren,
sodass 3 herauskommt und ich kriege eben das Ergebnis,
es ist die 2. Ich kriege auch keine Alternativen angeboten
Kann es eben auch benutzen, um so Mannenzerlegungen zu betreiben. Also wie kann,
welche beiden so Mannen muss ich miteinander addieren,
sodass die zwei herauskommt und dann ist eben das erste Ergebnis,
addiere die Null mit der zwei und du kriegst eine zwei heraus,
zweites Ergebnis,
addiere eine Eins mit einer Eins und es kommt zwei heraus und drittes Ergebnis,
addiere eine Zwei mit einer Null und es kommt auch die zwei heraus.
Und weitere Ergebnisse gibt es nicht.
Es gibt also drei Zerlegungsvarianten für diese Zahl hier in
natürlich zahlige Somaniten
Gut, wir müssen, oder nein, wir müssen nicht,
aber wir befassen uns mal wieder mit der Frage,
wie testen wir eigentlich ein Prädikat und wir führen jetzt hier das,
was sie aus SE1 schon kennen sollten, nämlich Regressionstests ein.
Wir haben ja immer als erstes uns Testfälle überlegt und wir haben zweitens die,
aus den Testfällen, die Prädikatsdefinition abgeleitet und wir können jetzt die Testdaten,
die wir uns mal überlegt haben und die wir aufschreiben,
haben tatsächlich auch benutzen, um zu testen,
ob das Programm sich so verhält,
wie uns wir uns gedacht hatten,
Und dann haben wir eben positive Testdaten und wir haben negative Testdaten.
Das sind genau die, die anfangs in der Tabelle drin standen.
Und wenn wir das gemacht haben, dann können wir sicher sein,
dass für diese Vollstuhlständig spezifizierten Aufrufe das Ganze richtig funktioniert.
Jetzt kommt aber, dass der Preis dazu,
den wir für die Logikprogrammierung zahlen müssen,
und den wir in anderen Kontexten so nicht haben,
wir müssen natürlich auch die Unterspitzwitze für Aufrufe testen.
Und das ist das, was die Arbeit, die neu hinzukommt.
Und die, das hat man ja schon gesehen,
eine gewisse Komplexität in sich hat, wir können natürlich auch
das Ganze iterativ betreiben und immer noch weitere Testdaten in die Datenbank einfügen.
Und eine einfache Testroutine, wie sie ihr Unit-Test sozusagen selbst schreiben können.
Und das ist eigentlich typisch für so ein Paradigma. Und die Art und Weise,
dass sie mit dem Interpreter die Programmiersprache selbst in der Hand haben,
ist hier eine einfache Testroutine, die einen Dutzend Meta-Programmierung,
die erst zum Schluss kommt.
Ich will ihn aber schon mal zeigen, was für nette Sachen man damit machen kann.
Das macht nichts anderes als ein Prädikat namens Test zu definieren,
was aus der Datenbank die Testdaten,
die wir ich eben hineingeschrieben habe, die also so hier aussehen,
wo immer drin steht, ich bin ein Testdatenelement und sage,
die null mit der Null addiert es null.
Ich bin auch ein Testdatenelement, was sagt,
die Null mit der Eins addiert es,
die Eins und so weiter und so fort.
Es nimmt also ein solches Testdatenelement heraus und ruft,
nimmt diesen Aufruf, der hier drin steht.
Denn das ist nichts anderes als das Ziel,
was sich in das System hineinstecke,
holt diesen Aufruf aus dem Dateneinbarg, Datenbank-Eintrag heraus,
das passiert hier.
Also gehe in diese relationale Datenbank, die nur eine Einzelspalte hat,
ein Argument und gib mir den Testaufruf,
der dort drinsteht
Und dann schreibe mir das Ding nochmal auf die Konsole aus,
damit ich sehe, wovon gerade die Rede ist.
Und gib mir das Diagnoseresultat dafür. Und dann,
dass das ganze Scheitern, und das Scheitern,
dieses Prädikat, hier scheitert immer, das führt dazu,
dass er jetzt versucht, Alternativen zu finden.
Das ist so programmiert, das werden wir gleich sehen,
dass es keine Alternativen gibt. Das ist ein eingebautes Prädikat,
das erzeugt per Definition keine Alternativen. Das wäre auch komisch,
wenn ein Reitprädikat Alternativen als euch. Also die einzigen Alternativen,
die es gibt, stecken hier in diesem Aufruf drin.
Jetzt geht jetzt er dadurch Schritt für Schritt durch die Datenbank durch,
nimmt jeweils eines der Elemente aus der Datenbank, wendet die Diagnose darauf an,
schreibt das Ganze auf die Konsole und macht mit dem Nächsten weiter.
Und die Diagnose, und das ist jetzt Meta-Programmierung,
nimmt dieses Teil, was in den Daten drin steckt,
aus der Datenbank und ruft es als allererstes auf.
Das ist die sogenannte Meta-Call-Facility. Hier wird also jetzt dieser
Ad-Something-Ausdruck in das System geschickt. Und wenn das positiv heraus
ausgeht, dann schreibe mir ein Yes auf die Konsole.
Und andernfalls, wenn es negativ ausgeht,
dann schreibe mir ein No auf die Konsole
Das ist alles. Und wenn ich das jetzt aufrufe,
dann habe ich eben diese Testfälle und kann hier hinten gucken,
ob das gewünschte Ergebnis dabei ist.
Das können Sie jetzt nach belieben, ausgebuffter machen.
Sie können natürlich bei den Testfällen immer gleich dazu speichern,
ob das ein positiver oder ein negativer Testfall ist.
Das habe ich hier nicht gemacht. Also Sie machen
das Ganze zweistellig, schreiben hier dahinter True oder Falls.
Und dann können Sie automatisch testen,
ob die entsprechenden Ergebnisse herauskommen oder nicht herauskommen.
Auf die Art und Weise kann man also insbesondere in Überschau,
weniger überschaubaren Fällen,
sich das Testen von solchen
vollständig instanzierten Aufrufen
etwas einfacher machen
Was man natürlich noch nicht erledigt hat, das sind die Tests
der runterspezifizierten Aufrufe und die darf man auf keinen Fall vernachlässigen.
Denn typischerweise treten die Terminierungsprobleme dann auf, wenn ich irgendetwas unterspezifiziert aufrufe.
Und dann muss ich mir überlegen, wie ich damit oder woran
es liegt und was man gegebenenfalls ändern kann oder ja,
nicht immer kann man das Problem beseitigen.
So, das ist also ein Blick gewesen in eine einfache,
rekursive Datenstruktur, PA-Notherme, mit denen man Arrithmetik betreiben kann.
Und wir könnten jetzt unsere gesamte Arrithmetik,
die wir im Rechner haben, auf Piano-Arrithmetik umstellen.
Was wären die Nachteile davon
Hm? Das war schon negative Zeit. Bitte? Negative Zahlen,
wo ich dir wert ist, wer das gefällt. Ja,
aber wir, wenn sich an die Schule erinnern,
sind da auch die natürlichen Zahlen als erstes eingeführt worden und
dann ist es erweitert worden auf ganze Zahlen,
indem man das Vorzeichen mit dazu genommen hat,
kann man in den Griff kriegen.
Es ist nicht sehr elegant, aber es geht.
Oder? Das ist immer vonständig schön. Ja,
das ist ausgesprochen ineffizient. Ich denke da nun einmal an
die Andition, jedes Mal wieder Schalen abbauen und aufbauen
zu müssen
Ja, sowohl der Rechenzeitbedarf, aber das liegt einzügig
allein daran, dass wir eine gute Konkurrenz haben.
Wir haben einen Arhythmetikprozessor, der solche Sachen wie addieren,
mit einer Operation machen kann. Und hier werfen wir ein
fürchterliches Geschütz darauf und zerlegen das Ding und bauen es ab,
bis wir bei der Null angekommen sind und so weiter.
Also klar, der Aufwand ist unbezahlbar, den wir da hätten.
Die eine Ausgabe sind auch noch nicht sehr schön verwehrt,
bis der Rüdner S von Null haben, uns das nicht auf Zahlen.
Genau, wir müssten es irgendwie transformieren, dass es vernünftig lesbar ist.
Stellen Sie sich die 1.5528, im Gegensatz zu 1.5029 vor.
Kein Mensch wäre in der Lage, festzustellen,
dass dort ein die Zahl ein bisschen größer geworden ist.
Also wir bräuchten etwas, was handhabbar ist,
unsere arabischen Ziffernsysteme sind natürlich erlauben, es sehr große Zahlen auch einigermaßen überschaubar darzustellen.
Was natürlich auch zu Buche schlägt, ist der Speicherbedarf.
Wir müssen hier für jedes, für jedes Inkrement von eins,
müssen wir eine Essschale bereitstellen. Das ist eine Struktur,
die aus einem Funktor und wenigstens einem Argument besteht.
Und das ist etwas, was man auch nicht bezahlen kann.
Der Speicherplatzaufwand ist ohnehin schon sehr groß und er steigt
Linie A mit der Größe der Zahl an
Das ist bei den Datentypen,
die wir sonst so kennen, natürlich nicht der Fall.
Was ist der Vorteil, wenn wir es so machen würden?
Es hat ja immer alles zwei Seiten. Also was würden wir
uns durch diesen Aufwand an einen Vorteil einkaufen, den wir sonst nicht hätten?
Kein Vorteil. Na gut, dann lassen wir es, ja.
Haben Sie einen? Wir haben ohne eben selber Spezial,
ohne Spezialfallbehandlung, die Überläufe, eine Atatmetik, die mit beliebig großen Zahlen arbeitet. Ja, genau. Also das ist,
wir haben keine Obergrenze für die Den Zahlenbereich im Gegensatz etwa zu Integr-Zahlen,
im Gegensatz zu Riel-Zahlen, wo wir immer sagen müssen,
wenn die 16,
32 oder 64 Bit erschöpft sind und die Geschichte der Rechentechnik zeigt,
dass wo immer ich die Grenze lege,
ich irgendwann in Schwierigkeiten komme, weil die Anforderung wachsen,
die Datenbestände werden größer und ich möchte plötzlich also auch eine,
bis dahin immer als ausreichend gegebene Grenze nach oben aufweichen.
Also alles hat Vor- und Nachteile. Natürlich würde keiner auf
die Idee kommen, jetzt die gesamte Rechentechnik, auch PA-Notzahlen umzustellen.
Wir haben es auch nur gemacht, um zu zeigen,
Wie geht man mit rekursiven Datenstrukturen um und wie baut man rekursive Prozeduren darüber auf?
Wir schauen uns jetzt als nächstes die Alternative an dazu
und die Alternative heißt natürlich, dass wir den Arrithmetikprozessor,
der hier drin ist, auch bei Kippe nutzen wollen.
Der Arrithmetikprozessor, der hier drin ist, ist aber leider nicht relational.
Achso, ja, einen Vorteil haben wir auch vergessen.
Die Richtungsunabhängigkeit. Ich kann also Addition betreiben,
auch in einer Weise, dass ich sage,
bitte zerlege mir eine Summe in ihre Summanden.
Das ist üblicherweise auch keine Operation von dem Arrithmetikprozessor unterstützt wird
Dieser Prozessor ist rein funktional. Ich gebe ihm zwei Sommanten und er gibt mir
die Summe. Und wenn ich diesen Mann nicht kenne,
dann wird der Arrathmetikprozessor sagen, dafür kann ich keine Summe berechnen.
Gib mir bitte die beiden Zahlen, sonst geht es nicht los.
Es wird ein Fehler geben. Und deshalb müssen wir jetzt dafür sorgen,
dass wir in der relationalen Welt, in der wir sind,
uns eine Ecke aufbauen, in der wir funktional arbeiten.
Wo wir jetzt also all das,
was wir bisher gemacht haben, nämlich ich möchte Richtungsunabhängigkeit machen,
ich möchte mit unterspezifizierten Ergebnissen Input arbeiten können,
all das gilt hier nicht mehr
Das ist das eine. Wir brauchen also eine solche Auswertungsumgebung.
Ich habe sie Ihnen auch schon gezeigt, an verschiedenen Stellen.
Das ist also nicht schrecklich Neues. Das ist schon in
der ersten Übung aufgetaucht. Wir müssen nur immer den Unterschied
genau klar machen. Arbeiten wir gerade funktional oder arbeiten wir relational?
Wenn wir funktional arbeiten, dürfen wir natürlich nichts erwarten,
was das relationale Paradigma charakterisiert. Also Richtungsunabhängigkeit und Unterspezifikation.
Das gibt es im funktionalen Modell nicht. Wenn wir relational arbeiten,
dann können wir natürlich davon ausgehen, So es umsetzbar ist,
dass wir die Eigenschaften zur Verfügung haben.
Die Hauptanwendung ist, dafür ist die, nein,
die 1CD Umgebung ist ausschließlich dafür gemacht, um Arrithmetik zu betreiben.
Deshalb nenne ich sie auch Arrithmetische Auswertungsumgebung. Und damit man dort
eben bequem arbeiten kann, müssen wir uns jetzt über arrithmetische Ausdrücke unterhalten.
Und dafür gibt es im Prolog-System ein Mechanismus, der nennt sich Operatoren. Die übliche Notationsweise,
die wir hätten, so etwas wie Ja,
also wir hatten relational ja die Schreibweise,
dass wir sagen mussten, add, 3, 2,
5. Das ist die Relation zwischen zwei so Mann und ihrer Sommer.
Wenn wir das jetzt funktional machen,
dann hätten wir also eine Funktion, die würde heißen,
add, na, drei hatten wir hier, zwei.
Ich gebe nur die beiden so Mann an und gib mir
bitte die Summe als Funktionswert dieses Aufruf, dieses Ausdrucks.
Das ist funktionales Denken. Und in der Mathematik oder wie wir
es sonst gewöhnt sind, wollen wir natürlich das so schreiben.
Und dann ist das
ein Operator
Ja, und wir müssen jetzt nach der Sorge tragen,
dass wir diese Operator und Notation in den Griff bekommen,
damit die Programme einigermaßen handhabbar und lesbar werden.
Operatoren sind etwas, was teils vordefiniert ist,
teils aber auch vom Nutzer hinzugefügt werden kann.
Das ist das erste Mal, dass wir einen Mechanismus kennenlernen,
wie wir die Programmiersprache selbst verändern können.
An dieser Stelle können wir hart in die Syntax der Programmiersprache
eingreifen und können uns Ausdrücke definieren, so wie wir sie gern hätten.
Wir gucken uns gleich Beispiele an. Also ich kann hier
die Syntax der Strache modifizieren, Beispiel,
dass ich eine übersichtliche Schreibweise einführen will oder dass ich
eine Nähe zur etablierten Notation haben will.
Also wenn in der Mathematik das so üblich ist,
will ich das in der Programmiersprache auch so haben.
Und das Ganze ist immer nur eine syntaktische Variante für eine
bestimmte ein- oder zweistellige Struktur.
So wie wir das gesehen haben. Dieser Operator ist nichts
anderes als eine syntaktische Variante einer zweistelligen Struktur, die so aussieht.
Intern im Prologsystem sieht das immer so aus.
Nach außen kriegen wir das so präsentiert.
Und dazwischen ist ein Formungsschritt dazwischen
Das Ganze sind syntaktische Varianten. Das heißt, dieser Operator hier
hat keinerlei Semantik. Das ist einfach nur eine Syntaxnotation,
die ich einführe, um statt das nicht hinschreiben zu müssen,
sondern das hinschreiben zu müssen. Eine Semantik muss sich erst
hinzufügen. Die ist nicht durch das System selbst gegeben.
Natürlich ist für den Operator plus eine Semantik vordefiniert.
Aber wenn ich jetzt meinen eigenen Operator für irgendeinen Zweck definiere,
dann darf ich nicht erwarten, dass das System weiß,
was ich damit zu tun beabsichtige,
sondern es erlaubt ab sofort nur,
dass ich Operator oder Ausdrücke
dieser Art verwende
Also hier müssen wir jetzt ganz sauber trennen, was ist Syntax-Eigenschaft der Sprache?
Ich darf eine bestimmte Struktur verwenden oder eine bestimmte Art von Ausdruck verwenden.
Und was ist diese Mantik? Was soll damit passieren?
Und die Semantik dieses Plus ist hier,
existiert in Prolog auch nur, in dem Augenblick,
wo ich es in die arathmetische Auswertungsumgebung stelle,
außerhalb dieser Auswertungsumgebung, hat es keinen, eine Bedeutung.
Also versuchen sich zu erinnern, allererste Übung,
da haben wir, da haben wir Experimente dieser Art gemacht,
dass wir gesagt haben,
was weiß ich,
drei ist,
Drei ist gleich 2 plus 1. Und es kommt ein False heraus,
weil eben hier ein rein so ein taktischer Vergleich gemacht wird,
es wird diese Zahl 3 auf die Struktur plus 2,1 unifiziert und das Scheitern.
Jetzt wissen wir, warum es scheitert. Damals haben wir
es nur als Effekt gesehen, dass es passiert.
Also dieses 2 plus 1 hat außerhalb der arethmetischen Auswertungsumgebung keinerlei Bedeutung,
die arethmetische Auswertungsumgebung, das habe ich ebenfalls an verschiedenen Stellen gesagt,
ist, dieses ist, wenn ich das der rechten Seite und ist es auch nur ein Operator.
Wenn ich auf der rechten Seite unter den Operator ist,
stelle, dann wird es ausgewertet.
Und dann hat es eine Semantik und dann kommt plötzlich Troh raus,
weil nämlich, und das können wir sehen,
in dem Augenblick, wo wir hier eine Variable einführen,
die bei Jahren wieder den Wert 3 bekommt.
Also, solange wir relational arbeiten, haben Operatorstrukturen keine Semantik,
solange, wenn wir sie nicht hinzufügen.
Wir können natürlich jederzeit eine Prädikatsdefinition bauen,
um das Ganze mit Semantik zu unterfüttern.
Aber als Operatorstruktur ist es erstmal nur eine reine syntaktische Notationsvariamte
Hallo, ja, will ich hin. So,
ja, wenn ich also einen Operator selbst definieren möchte,
dann muss ich das so, upsala,
dann muss ich das so machen, dass ich,
nein, das ist noch nicht die Definition.
Erstmal müssen wir verschiedene Operatoren voneinander unterscheiden.
Und zwar gibt es grundsätzlich aus der Mathematik,
kennen Sie Beispiele dafür, Prefix-Operatoren, Infix-Operatoren und Post-Fix-Operatoren,
Post-Fix und Prefix-Operatoren sind immer einstellig.
Das ist also so etwas wie Logarithmus.
Da steht erst der Operator da und dann kommt der Operant
Oder Kokosfix-Operatorin, sowas wie die Fakultät. Da steht erst
der Operant und dann der Operator. Und dann die zweistelligen
Infix-Operatorinnen, wo der Operator zwischen den beiden Operanten steht.
Das sind die drei Formen, die wir hier haben.
Und die haben Schlüsselwörter, die heißen also,
wenn ich sie definiere, ist der vom Typ FX,
also erst Funktor, dann ein Argument,
der ist Infix-Operator, also erst Argument,
dein Funktor, dann wieder Argument.
Und der ist Postfix, erst Argument, dann Funktor.
Das sind Schlüsselwörter, mit denen ich den Typ des Operators
dem System bekannt geben kann
Es gibt dann etwas von Operator Präzedenz. Das kennen wir
alle aus der Grundschule noch. Punktrechnung geht vor Strichrechnung.
Das muss man sowohl dem Schüler als auch dem System beibringen.
Und das wird hier dadurch gemacht, dass es eine Zahl gibt,
die sagt, welche Operatoren binden stärker als die anderen.
Und die Vereinbarung ist hier, das ist ein bisschen Counter-intuitive, geringere Werte binnen stärker.
Also das sind die Werte, die im System vorgegeben sind.
Die sind voreingestellt, die Multiplikation und die Division,
also Punktrechnung, findet stärker als die Strichrechnung Additionen und Subtraktionen.
Die kriegt den Wert von 100, die kriegt den Wert 400.
Und da gibt es eine Hierarchie,
das ist auch mal vereinbart worden zwischen 1 und 1200,
fragen Sie mich nicht, warum gerade
1200. Und in diese Hierarchie kann ich eben neue Operatoren einbauen.
Und dann sorgt mir diese Operatorpräzedenz dafür,
dass dieser Ausdruck hier A plus B mal C so geklammert wird.
Hier wird schon deutlich, dass dieses Plus hier nichts mit arithmetischer Operation zu tun hat.
Er sagt nur etwas über die Struktur aus,
dass eben die interne Struktur dieses Ausdrucks Eben ist der oberste Operator,
ist die Multiplikation, darunter steht, Quatsch, umgekehrt.
Ich muss zuerst die Multiplikation durchführen, von,
was haben wir, B und C.
Und dann kann ich das Ergebnis per Addition mit Arthur bedienen.
Diese syntaktische Struktur, die der Auswertung zugrunde liegt,
die wird durch die Operatorin definiert.
Aber es wird hier noch nichts ausgewertet an so einer Stelle.
Erst wenn ich es in die Auswertungsumgebung stelle,
dann wird der Wert
tatsächlich ermitteln
Gut, wir haben dann symmetrische Operatoren, die sind nicht assoziativ,
die kann ich also nicht reihen wie 1 plus 2 plus 3 plus 4 plus 5,
sondern da kann ich wirklich immer nur zwei Elemente miteinander verknüpfen.
Also Operatoreinwettung ist nicht möglich und kleiner als ist als solcher
symmetrischer Operator definiert, also eine solche Kette geht nicht.
Während 1 plus 2 plus 3 geht, das hängt eben vom Typ,
der der Operatoren ab. Wir nehmen mal als Beispiel etwas,
wir hatten ganz zu Anfang ja die Datenbank,
mit deren Liebprädikaten drin und wir wollen dieses Lied als Infix-Operator definieren,
weil dann passiert, dass durch diesen Aufruf am System prompt,
dass wir sagen, wir definieren liebt vom Typ XFX,
das heißt also als nicht-assoziativer Operator, und er kriegt eine Präzedenz 300,
das heißt, er bindet stärker als Addition und Multiplikation.
Und dann sind wir in der Lage,
nachdem wir jetzt hier unsere Fakten eingelesen haben,
das ist also ein Ausschnitt aus unserem Pfeil,
was wir einlesen, das ist die Verwendungsweise einer Direktive.
Dieses Doppelpunkt vor einem Eintrag in einer Datei, die per Consult eingelesen wird,
heißt, arbeite dieses Teilziel ab und zwar sofort.
Und dadurch wird diese Operator definiert und jetzt kann ich ab sofort,
akzeptiert das System, Ausdrücke der Art Operator, Operant, Operator.
Ja, ja. Operant, Operator, Operant.
Und ich kann genauso gut natürlich noch die alte Notationsweise verwenden
mit Funktor Forn und Argumente gefolgt in der geklammerten Liste.
Und wenn ich jetzt ein Listing mache,
dann gibt er mir aufgrund dieser Definition für den Operator liebt,
das Ganze auch in Operator Schreibweise aus.
Also jetzt kommen tatsächlich Ausdrücke dieser Art aus dem System raus,
von Hans liebt Geld und Hans
gibt Susi
Obwohl ich das eine Mal in Operator schreibe,
also eingegeben habe und das andere in functo Argument schreibweise angegeben habe.
An der Stelle sieht man, dass das Ganze nur so ein taktischer Zucker ist.
Und er gibt mir diesen syntaktischen Zucker auch im Endeffekt wieder aus.
Ich kann jetzt diese Operatoren in einem, als Klauselkörper verwenden,
also ich kann die Anfrage jetzt auch so formulieren,
X, also Argument, Functo, Argument,
X liebt Susi und kriege die Antwort Hans oder ich kann
es eben auch auf klassische Art und Weise machen,
indem ich die Funkta-Argument schreibtweise mit der geklammerten Argumentliste verwende.
Es gibt dann die assoziativen Operatoren, die asymmetrischen, die eine Beeinbettung ermöglichen.
Und dann muss man unterscheiden zwischen dem Typ Y FX und dem Typ XFFY.
Und das sagt nur, ob es links- oder rechtsassoziativ ist.
Also ob der Baum so aufgebaut wird oder ob er so aufgebaut wird.
Mit anderen Worten, die Frage, in welcher Reihenfolge,
wenn man das arithmetisch, den Begriff einer aribetischen Auswertung sieht,
in welcher Reihenfolge wird die Summe gebildet,
indem ich von links nach rechts abarbeite,
so wie der Taschenrechner beispielsweise oder ob ich das von hinten
nach vorn abarbeite
Das ist ein Blick in die Liste der vordefinierten Operatoren,
nur damit man meinen Überblick hat. Und die erste Überraschung ist,
die Klauselstruktur, also unser Kopfseparator, unser Semikolon,
unser Komma, sind alles nichts anderes als Operatoren.
Ich kann also genauso gut klauseln, auch in Funkteargumentschreibweise eingeben.
Das geht sehr gut. Das einzige Problem ist,
dass ich aufpassen muss, dass ich nicht mit der klassischen Sonntags durcheinanderkomme.
Wenn ich also einen Funktor Komma verwende, muss ich verhindern,
dass er mit dem Komma verwechselt wird.
Also ich muss das Ganze in Hochkomma setzen,
aber ansonsten ist es kein Problem,
dass ich eben einen Ausdruck dieser Art als Ziel eingebe,
der da lautet, Komma, Teil Ziel 1,
Teil Ziel 2. Das heißt dann,
ich bin eine Konjunktion von dem und von dem.
Oder, wenn man möchte, auch Semikolen oder was auch immer.
Oder, wenn man möchte,
eben eine Klausel-Definition dieser Art, heißt dann,
A von 1,
Funktor mit Körper A von
Macht natürlich mit den Konstanten keinen Sinn,
aber nehmen wir mal sowas an hier.
Na, X sollte das sein. Jetzt wird es
immer verrückter. A, von XB, von X.
Das wäre nichts anderes als eine andere syntaktische Notationsweise für unsere
klassische Klauselnotation
Also, erste Botschaft, auch unsere gesamte Syntax der Klauseln,
ist nichts anderes, sind nichts anderes als Operator, Notationen.
Und jetzt sehen wir eben, der Kopfseparator darf nicht eingewettet werden.
Also ich kann keine Sequenz von, er gibt sich zu,
er gibt sich zu, ergibt sich zumachen.
Das ist nicht erlaubt. Während Semikolons,
kann ich in beliebiger Anzahl aneinanderreihen und komme das genauso.
Das sieht man an der Stelle. Dann eben,
was wir gesehen hatten, die Direktiven,
also was man in eine Datei einschreiben kann,
hat die gleiche Bindung wie der Kopfseparator,
ist aber
nur ein Präfix-Operator
Die Negation, das ist das Not Plus oder eben das alte Not,
sieht man Licht in der Hierarchie, etwas unterhalb der Konjunktion und der Destitionktion.
Dann kommen die ganzen Sachen,
die mit Unifikationen und so weiter zu tun haben,
dann kommen die numerischen Vergleichsoperatoren und arithmetische Funktionen rangieren in dieser Hierarchie relativ weit unten.
Und dann haben wir eben auch hier wieder die asymmetrischen Operatoren,
und zwar Strichrechnung, die Schwachbinden plus und Minus,
die arithmetischen Operationen, die stark binden, modulo,
ob es modulo seid, aber nicht XFY sein.
Nicht asymmetrisch sein. Nee, das ist die ganze Zeit
hier, was macht das für einen Sinn?
Die ganze Zeit die Divisionen zu reihen.
Das ist die Garten, die Integrale Vision des Doppelt,
der Doppelstrich. Gut, das muss man nochmal nachgucken.
Modulo-Rechnung ist tatsächlich eben nicht zum etrischen. Ist symmetrisch,
ist nicht assoziativ. Und das Potenzieren kann ich auch reihen.
Und das Potenzieren sieht man, bindet am allerstärksten,
das muss
zuerst abgearbeitet werden
Da hat man mal einen Überblick davon, wie die Hierarchie
da eigentlich bestellt ist. Und das hatte ich schon angedeutet,
auch unsere klassische Klausel, Syntax ist eben nichts anderes als
intern eine solche Struktur mit einem Funktor, Kopfseparator,
einem zwei Argumenten, nämlich dem Kopf und dem Körper.
Und der Körper ist eben genau diese Konjunktion von zwei Teilerzielen.
Intern wird das so verwaltet,
extern kriegen wir das zu sehen,
weil eben
die Dinge als Operator definiert sind
Ja, und dann durch die Meerstufchenpräferenzen kriegt man also insgesamt
die syntaktische Struktur raus und von daher weiß man,
was man damit auch tatsächlich anfangen soll.
In der Tabelle ist ein spezieller Operator aufgetaucht, das ist der strukturelle Vergleich.
Auch den gab es schon auf dem ersten Übungszettel zu sehen,
das ist die sogenannte Standard, die Ordnungsrelation über der sogenannten Standardordnung der Therme.
Und da hat man irgendwann mal sich darauf geeinigt,
was eine Standardordnordnung ist, die sieht immer vor,
dass die Strukturen, die am weitesten oben in der Hierarchie stehen,
gefolgt von den Zahlen,
gefolgt von den Atomen,
gefolgt von den Vagablen
Also eine Variable ist mit diesem Operator verglichen immer kleiner als ein Atom,
ein Atom ist immer kleiner als eine Struktur.
Ich kenne keine sinnvolle Verwendung für den ganzen Spaß,
außer Sortierung nach lexikalischen, nackt sexikalischer Ordnung.
Das ist ein Nebeneffekt, den man damit erzielen kann.
Also so etwas wie A-A-A ist eben kleiner als A-B-C,
weil das lexikalisch davor einzusortieren ist. Dafür ist es ganz gut zu gebrauchen. Alles andere ist,
Kann man zur Kenntnis nehmen, dass das so ist,
also was weiß ich, diese Strukturen hier werden beispielsweise verglichen,
das ist klar, hier, das ist der Typ,
übergreifender Vergleich, Atom ist immer kleiner als Struktur,
Variable, immer kleiner als Atom.
Hier haben wir zwei Strukturen, die aufeinandertreffen und dann gilt,
sortiere zuerst nach dem Funktor und anschließend nach den Argumenten.
Also das ist kleiner, weil A, kleiner als B ist.
Wenn ich den Funktor gleich habe, dann zählt Stelligkeit.
Also einstelliges, kleiner als zweistellig. Und wenn auch die Stelligkeit gleich ist,
dann sortiere nach den Argumenten, dann ist eben A von B,
Komma C kleiner als A von B,
Komma D,
weil hier hinten C,
kleiner als
Wie er es liebt, kann es benutzen. Ich weiß,
wie gesagt, außer lexikalischer Sortierung keine richtige Anwendung dafür.
Was für uns wichtig ist, ist, dass Strukturen tatsächlich
die einzige Basisstaatenstruktur ist, die wir haben in der Logikprämierung.
Etwas anderes gibt es nicht. Alles sind Strukturen.
Auch unsere Atome sind nullstellige Strukturen. Und die Operatoren,
die wir bisher immer verwendet haben, sind nur syntaktische notationsvarianten,
um das Ganze angenehm handhabbar zu machen.
Wir können also aufgrund dessen auch syntaktisch Daten und Programme nicht voneinander unterscheiden.
Es gibt kein Kriterium, was wir sagen können,
dies ist ein Datenelement, das ist ein Programme.
Und das erlaubt es uns eben, prädikalste Definitionen und auch Prädikatsaufrufe dynamisch zu erzeugen.
Einen kleinen Vorgeschmack haben wir vorhin gerade bei der Testroutine gesehen.
Wir nehmen etwas aus der Datenbank und schicken es sofort
als Anfrage auf die Datenbank los.
Wir können auf die Art und Weise dann Daten und Programme wechselseitig ineinander umwandeln,
diese Meta-Call Facility hatten wir gerade gesehen,
nimm irgendein Datenelement und rufe es auf
Und wir können aber auch Programmklauseln als Daten interpretieren und
das werden wir bei der Metaprogrammierung in Extenso machen,
dass wir eben einen Aufruf machen, gib mir alle Klauseln,
die auf einen bestimmten Kopf unifizieren.
Oder gib mir alle Klauseln, die einen bestimmten Eigenschaften im Körper haben.
Da können wir uns die Klauseln aus der Datenbank rausholen.
Können sie zerpflücken? Können sie andersrum wieder zusammensetzen und wieder in die Datenbank reinschreiben?
Das Ganze ist aber rein syntaktisch, was wir machen.
Das hat noch keiner der Semantik, die damit verbunden ist.
Das ist eben genau diese Gegenüberstellung von arithmetischer Operatorstruktur.
Ich möchte zwei mit drei addieren
Und was wir eigentlich haben, ist ein dreistelliger Relation,
die Relation zwischen den beiden Sumannen und der Summe.
Und wenn ich tatsächlich mit diesem Operator ausdrücken,
so wie wir sie aus der Mathematik kennen,
arbeiten will, dann brauche ich eine funktionale Auswertung.
Und dafür brauchen wir eine spezielle Umgebung, die sagt,
in dieser Umgebung wird nicht mehr relational gedacht, sondern funktional.
Und damit haben wir ein lokales, funktionales Verarbeitungsmodell im relationalen Paradigma.
Das ist ein sehr eingeschränktes Verarbeitungsmodell, was nur dafür
da ist, den Wert eines arithmetischen Ausdrucks zu ermitteln.
Also alle anderen Sachen, die man in der funktionalen Programmierung machen kann,
Funktionen als Bürger erster Klasse und so weiter, kann man sich abschminken.
Das geht damit nicht. Das ist ganz special purpose,
nur für diesen einen Zweck gemacht. Auswertungsumgebung ist dann ein spezieller Infix-Operator,
der genau diesen arithmetischen Wert von funktionalen Ausdrücken ermittelt.
Der klassische Vertreter ist, dieses ist,
und zwar ein asymmettischer Operator,
der für die linke und die Rechte um Seite unterschiedliche Bedingungen stellt,
weil nämlich rechts wird arithmetisch ausgewertet, links wird unifiziert.
Das ist genau die Verbindung zwischen relationaler und funktionaler Welt,
die wir brauchen. Wie kriegen wir Werde, die aus
der funktionalen Welt kommen, in die relationale Welt hineingebracht.
Dafür brauchen wir dieses Iss. Es gibt noch weitere Verwendungsweisen
dieser arathmetischen Auswertungsumgebung in den Vergleichsoperatoren. Da ist es wieder
symmetrisch. Aber hier ist es eben diese Mantik,
dieses Ausdrucks ist Werte aus, was du hier fit vorfindest.
Und das hat natürlich auch nur einen Wert, wenn ich ihn ermitteln kann.
Wenn ich hier beispielsweise unterspezifizierte Ausdrücke drin verwende,
nicht instanzuierte Variablen, dann muss ich mich nicht wundern,
dass die Auswertungsumgebung sagt, tut mir leid,
damit kann ich nichts machen.
Damit kann ich
nichts anfangen
Also das wird ausgewertet und der Wert, der hier herauskommt,
der wird unifiziert, mit was immer auf der linken Seite steht.
Die Auswertung benutzt dann tatsächlich den Athmetikprozessor und nicht irgendein anderes, abgefahrenes Modell.
Und die Konsequenzen sind eben, weil dieser Athmetikprozessor strikt funktional arbeitet,
darf auf der linken Seite auch keine Struktur stehen,
sondern da kommt ein arithmetischer Wert heraus.
Und ein arithmetischer Wert mit einer Struktur unifiziert scheitert immer,
dann kann ich auch gleich Fail reinschreiben.
Dann brauche ich gar keinen Ausdruck zu machen. Also auf
der linken Seite darf keine Struktur stehen, vaiable oder eine numerische Konstante.
Das ist das Einzige, was sinnvoll ist.
Die vaiable wird dann an dem Wert mit dem Wert versehen.
Oder aber wenn eine numerische Konstante da ist,
dann würden eben die beiden auf Unifikationen geprüft.
Da steht eben eine 4 und rechts ergibt sich aus der
Auswertung eine 3, dann unifiziert es nicht,
wenn die Auswertung auch eine 4 ergibt, dann unifiziert ist.
Die rechte Seite darf natürlich Vajablen enthalten, alles andere wäre schlimm,
aber sie müssen immer bereits instanziiert sein.
Und an dieser Stelle zerschießt es uns die Richtungsunabhängigkeit der Logikprogrammierung.
Jetzt werden Beschränkungen für die Aufrufvarianten formuliert, indem wir das Ganze noch verwenden können.
Wenn irgendwann mal eine arhethmetische Auswertungsumgebung auftaucht, geht die Richtungsunabhängigkeit den Bach runter.
Damit muss man leben. Das ist das, was im letzten Satz hier steht. Daraus folgt jetzt,
dass das ist, also diese Auswertungsumgebung fehlschlägt,
wenn irgendeine Typrestriktion verletzt wird,
also wenn auf der linken Seite etwa ein komplexer Term steht,
wenn linke Seite und Wert nicht miteinander unifizieren,
also wenn da eine Ungleichheit vorliegt,
dann scheitert die Unifikation auch
Das heißt nicht, dass da ein Fehler passiert.
Wir müssen in der Logikprogrammierung immer sauber unterscheiden zwischen die Unifikation
scheitert, was ein ganz alltäglicher Vorgang ist.
Unifikation scheitern, laufende Meter. Das haben wir in den Traces
gesehen. Ich fasse ein Klausel an und stelle fest,
oh, leider nicht einschlägig. Also nimm die nächste.
Wir haben ja immer viele Alternativen, die wir überprüfen können.
Also da ist ein Fehlschlag da. Wir müssen das sauber
unterscheiden von dem Zustand, wo ein Fehlfehler tatsächlich passiert.
Wo das System mit einer Fehlernachricht abbricht.
Und das passiert immer dann, wenn ich Auswertung fordere,
aber sie ist nicht herstellbar
Wenn das gar kein arrithmetischer Ausdruck ist,
das System sagt dann, something is not a function,
dann lässt sich kein arrithmetischer Wert ermitteln.
Oder aber, wenn es uninstanzierte Variable enthält,
dann ist die Fehlernachricht sowas wie Arguments Annots Afficiently instantiauated.
Das sind die beiden Fehlerfälle, in denen man laufen kann
bei der Gelegenheit. Wir sollten auch uns darüber klar sein,
dass das, es sieht aus wie eine Agyptanweisung.
In vielen Fällen wird es auch wie eine Agyptanweisung benutzt,
aber es ist mehr als die Ergibtanweisung in imperativen Sprachen.
Also insbesondere Ja, das kann man in dem Fall diskutieren.
Hier, hier sind mal ein paar Beispiele aufgeschrieben. Also dieser Ausdruck sagt,
Werte auf der rechten Seite aus, Punktrechnung geht für Strichrechnung,
also zweimal fünf bis zehn, minus sieben ist drei,
wird unifiziert mit der linken Seite und das ist erfolgreich.
Hier wird Punktrechnung Verstrichung, dreimal zweimal drei,
erst sechs plus eins ist sieben und dieser Wert wird mit dieser Variablen udifiziert,
sodass diese Variablenbindung hergestellt wird, ab sofort hat X den Wert sieben.
Hier wird auf der rechten Seite berechnet,
das ist also wieder die drei und das wird unifiziert mit
diesem komplexen Term und das scheitert
Auf der linken Seite wird nicht ausgewertet, nur auf der rechten wird ausgewertet.
Es ist das Bindeglied, relational links, funktional rechts.
Das muss man sich immer einhämmern. Viertes Beispiel.
Jetzt eine Sequenz, zwei HS-Ausdrücke. Durch diesen ersten Ausdruck
wird X an 5 gebunden. Wenn ich jetzt hier zweimal
6 zu 12 auswerte und dies mit X odifizieren soll,
hat X bereits den Wert 5 und dann scheitert die Olifikation,
weil ich hier bereits einen Wert an die Variable gebunden habe,
die damit nicht kompatibel ist,
mit dieser Auswertung
nicht kompatibel ist
Hier haben wir X wird mit dem Wert 1 versehen und
dann wird X um eins erhöht sozusagen.
Hier wird also der Wert von X genommen, um eins erhöht.
Das heißt also zwei. Und dann soll es mit X
udifiziert werden. Das hat aber immer noch den Wert eins.
Und an dieser Stelle sehen wir den Unterschied zur imperativen Programmierung.
Da ist er mit aller Deutlichkeit. Also wann immer ich,
je nach Sprache, in den klassischen Programmiersprachen sowas schreibe,
wie X, ergibt sich zu X plus eins,
in verschiedenen so taktischen Varianten oder eben Inkrement von irgendetwas hinschreibe,
dass es scheitert in der Logikprogrammierung immer
Also wann immer ich so etwas habe, wie X,
ergibt sich zu X plus 1, das ist immer falsch,
weil das um eins erhöhte X nicht mit dem ursprünglichen X unifizieren kann,
per Definition nicht.
Also wer so etwas hinschreibt,
der kann auch gleich ein Fail reinschreiben als Programm.
Jetzt machen wir das Gleiche. Im zweiten Falle jetzt multiplizieren
wir also das X mit 1 und plötzlich gelingt es,
warum. Ja, jetzt wird es bei uns raus,
kommt X,
ist in Jesu
bei uns
Also, die Eins ist hier, die Eins ist das
neutrale Element der Multiplikation und durch Zufall gelingt es.
Jeder Versuch ist mit etwas anderem zu multiplizieren, würde natürlich scheitern.
Aber weil wir zufällig das neutrale Element gewählt haben, gelingt es.
Also dieses X mit Eins multipliziert verändert seinen wertlich und
das inifiziert also immer noch mit dem ursprünglichen Vayablen Belegung.
Ja, wenn ich das nur eingebe,
dann ist Y U sitzanziiert, dann kriege ich eben die Beschwerde,
dass es die Argumente nicht sufficiently instantiated sind.
Und wenn ich dieses habe,
dann kriege ich das A in or the function ist,
weil er versucht A als arrithmetischen Ausdruck zu interpretieren und
es ist keiner
Er kriegt keinen arathmetischen Wert für eine alpha-numerische Konstante.
Und das liegt führt auch zu einem Fehler. Es gibt
auch die arathmetischen Vergleichsoperatorinnen und auch die verwenden diese Auswertungsumgebung.
Diesmal aber in symmetrischer Art und Weise,
dass sie nämlich sowohl links als auch rechts auswerten.
Also es sind drei Sachen, das testet auf arathmetische Gleichheit.
Das testet auf kleiner als das Tastet-Off größer als.
Wenn ich also so etwas habe wie zehn geteilt durch zwei,
und das wird ausgewertet,
ergibt fünf und auf der rechten Seite ergibt die Auswertung Peer Plus 1,
fünf,
dann unifiziert das Miteinander und
das Ganze ist erfolgreich
Also an der Stelle kann man sich mal sehr schön überlegen,
was ist der Unterschied zwischen dem Gleichheitszeichen für die Unifikation
und die sind gleich Doppelpunktgleich für den arithmetischen Vergleich.
Sind die beiden arithmetisch gleich? Das wertet aus,
das Gleich würde nicht auswerten, dann wird die syntaktische Struktur
links mit der syntaktischen Struktur rechts verglichen und die sind natürlich nicht unifizierbar.
Weil schon der Funkler unterschiedlich ist, hier ein Schrägstrich hier ein Pluszeichen.
Oder eben hier da 10 minus 2 ist 8 und 4
mal 1 ist 4, das scheitert natürlich,
weil die bildlich gleich sind
Weil das Ding symmetrisch ist, kann es nicht zu vajablen Instanzsicherung verwendet werden.
Also wenn immer ich so etwas mache wie X,
soll zweimal drei minus eins sein,
dann kriege ich genau diesen Fehler,
dass die Argumente nicht hinreichend instanziert sind, weil er nämlich versucht,
den Wert auf der linken Seite zu ermitteln und das geht nicht,
das scheitert.
Er hat keinen Wert dafür und darüber beklagt er sich dann auch.
Also wenn nicht eine Vagable mit einem Wert versehen will,
eine Wertezuweisung sozusagen, brauche ich,
das ist diese Asymmetrik, diesen asymmetrischen Operator.
Der ist wirklich nur dazu da,
um symmetrisch zwischen numerischen Werten hier und numerischen Werten
dort zu vergleichen
Alles, was ich hier diskutiert habe, gilt auch für kleiner
als größer als kleiner gleich und so weiter und so fort.
Die Konsequenz von dem, was wir eben gemacht haben,
ist also, dass wir jetzt eine hybride Sprache haben,
was durchaus nichts ungewöhnlich ist, ist alle unsere Programmiersprachen sind hybride Sprachen.
Wir haben ein relationales Modell und in diesem relationalen Modell machen wir
eine kleine Ecke auf, in der jetzt funktional gearbeitet wird.
Und zwar nur für einen ganz, ganz kleinen, speziellen Zweck.
Und die Restriktionen, die in dieser funktionalen Ecke stecken,
die sind eben genau ein Reflex der Hardware-Architektur, die wir haben.
Die Hardware, die wir haben,
kann nichts anderes als das,
was dort zur Verfügung gestellt wird
Und dort wird nämlich Arrithmetik mit außerlogischen Mitteln behandelt.
Wenn wir es mit logischen Mitteln behandeln wollten,
dann bräuchten wir sowas wie die PANO-Arrithmetik. Die ist logisch sauber
und dann haben wir auch die Richtungsunabhängigkeit wieder im Kasten.
Aber solange wir uns auf diese Hardware-Architektur beziehen und wir wollen
das, müssen wir mit den Restriktionen leben.
So, und damit geht es jetzt los. Jetzt machen
wir tatsächlich Arrithmetik im Rahmen der Logikprogrammierung. Wir benutzen,
damit das Ganze überhaupt sinnvoll ist, weitgehend wieder rekursive Programmschemata,
also die Aufgaben sind alle aus dem Bereich,
wie kann man rekursiv etwas berechnen
Und die einfachste rekursive Funktion, die ich kenne,
ist, der ist die Fakultät, die ist hier definiert.
Und hier haben wir den schönen Vorteil,
im Gegensatz zu den PA- und Prädikaten, die wir vorher hatten,
wir können hier ganz einfach ins Mathematikbuch schauen und gucken,
wie ist die Fakultät eigentlich definiert.
Und sie ist definiert, als sie ist eins,
wenn N gleich Null ist und sie ist N-minus eins Fakultät,
mal N für alle anderen Fälle.
Und dann haben wir hier, das ist eine induktive Definition,
die sagt uns, hier fangen wir an,
Pakultäten zu generieren und in denen wir immer den nächsten Faktor drauf multiplizieren,
kriegen wir beliebig große Fakultäten ausgerechnet.
Also wir haben einen Induktionsanfang,
wir haben einen Induktionsschritt
Wir müssen jetzt zwei Sachen machen, in der funktionalen Programmierung können wir
das Ding nehmen und unmittelbar in ein funktionales Programm umsetzen.
Jetzt machen wir relationale Programmierung, wir müssen es in ein
relationales Programm umsetzen. Und das sollte inzwischen schon aufgefallen sein,
wenn wir Addition machen, wird eine zweistellige Funktion zur dreistelligen Relation.
Wenn wir Fakultät berechnen, wird eine einstellige Operation zu einer zweistelligen Relation,
weil ich nämlich jetzt die Relation zwischen einer Zahl und ihrer Fakultät beschreiben muss.
Also das ist immer, wenn ich einen funktionalen Zusammenhang mit relationalen Mitteln replizieren will,
brauche ich ein zusätzliches Argument für das Ergebnis.
Sonst habe keine Möglichkeit, das Ergebnis nach draußen mitzuteilen.
Also wir kriegen jetzt ein zweistellige Relation zwischen einer Zahl und ihrer Fakultät.
Und das ist eben, das Ergebnis ist gleich eins,
wenn N gleich Null ist.
Und andernfalls müssen wir die Fakultät von N-1 berechnen.
Wir kriegen ein Zwischenergebnis R1. Und das Gesamtergebnis ergibt sich dann daraus,
dass wir dieses Zwischenergebnis mit dem aktuellen Ende nochmal multiplizieren.
Das ist diese funktionale Definition in eine relationale Definition umgewandelt.
Und der Witz ist eben hier,
dass wir hier den rekusiven Aufruf für die Fakultät haben,
der ist hier auch da
Aber hier können wir eben das Ergebnis sofort
in einem Ausdruck mit dem neuen Faktor multiplizieren.
Das geht hier nicht. Wir müssen das Zwischenergebnis irgendwo festhalten,
als wäret einer Variablen und können es dann weiterverwenden in einem zweiten Teil erzielen.
Und jetzt gehen wir einfach hin und übersetzen das in ein Logikprogramm.
Hier oben ist nochmal die Definition angegeben. Hier unten ist
das Logikprogramm. Das sagt eben, wenn N gleich Null ist,
dann ist das Resultat 1 und das ist unser Rekursionsabschluss.
Wenn N gleich Null ist, ist die Fakultät davon 1.
Und ansonsten berechne eben die Fakultät von
Dafür muss ich erst mal N minus eins berechnen.
Das passiert hier, indem ich also von N1 abziehe.
Und dann berechne ich für dieses N1, das heißt um eins reduzierte N die Fakultät.
Ich kriege das Zwischenergebnis, was hier oben mit R1 bezeichnet ist,
das habe ich hier Fuck N1 genannt.
Und daraus kann ich dann durch Multiplikation mit dem aktuellen N
das Ergebnis, die Fakultät berechnen.
Der Sicherheit halber habe ich hier noch mal ein Guard reingeschrieben,
das das Ganze nur für natürliche Zeilen definiert ist und nicht für negative Zahlen,
was insbesondere auch hilfreich ist, um die Terminierung sicherzustellen.
Also wir müssen uns mal überlegen, was würde passieren,
wenn wir dieses hier nicht vorsehen, dann könnte es passieren,
dass wir in bestimmten Aufrufen versuchen würden,
diese Null durch Abziehen von eins zu verkleinern.
Dann kriegen wir eben minus eins raus und rufen dafür,
dass alles wieder auf und so weiter und so fort.
Und kommen praktisch nie nochmal an dem Rekulsionsabschluss vorbei. Das heißt,
wenn wir dieses nicht testen, öffnen wir die Tür,
dass das Ganze nicht mehr terminiert, das können sie sich in Ruhe mal überlegen.
Wichtig ist hier, dass Rekulsionsschema, was wir uns mal angucken,
wir rufen also das Ganze beispielsweise auf,
wir wollen die Fakultät von vier wissen,
und führen es durch Subtraktion von eins auf die Fakultät von drei zurück.
Durch Subtraktion von eins auf die Fakultät von zwei,
Durchsubtraktion von eins auf die Fakultät von eins und kommen dann am Rekursionsabschluss an und sagen,
ja, die Fakultät von 0 ist 1. Und jetzt müssen wir anschließend wieder nochmal mit eins,
mit zwei, mit drei, mit vier multiplizieren.
Lebt es noch? Ja, gut. Hier haben wir
jetzt plötzlich ein neues Bild. Während wir vorher bei diesem Piano-Predikaten
Strukturen von außen nach innen aufgebaut haben und bereits im ersten Schritt wussten,
wie das Ergebnis aussehen wird, nur die Details noch nicht kannten,
Wenn wir jetzt, dass das Ergebnisargument immer uninstantisiert bleibt.
Und auch wenn ich hier unten bin, kann ich die Berechnung keinesfalls abschließen.
Ich muss anschließend noch wieder dieses Multiplizieren vornehmen,
um das Ergebnis berechnen zu können.
Hier erfolgt die eigentliche Berechnung also nur beim rekursiven Aufstieg.
Beim Abstieg wird eigentlich nur gezählt, bis ich irgendwann mal
den Rekursionsabschluss gefunden habe. Und das wird,
wenn die ganze Berechnung nur hier im Rekursiven-Aufstieg passiert,
auch als Induktion bezeichnet und ist der Spezialfall einer schlichten Rekursion,
wo nämlich immer nur auf einem Zweig berechnet wird.
Es gibt auch den umgekehrten Fall,
wo nur beim Abstieg berechnet wird,
den gucken wir uns als nächstes
dann gleich an
Also schlicht Rekursion heißt,
es wird nur auf einem Ast der Rekursion berechnet,
also nur beim Abstieg oder nur beim Aufstieg.
Und wichtig ist dabei immer, dass es nur ein auf
rekursiven Aufrufbüroplausel gibt, das ist hier tatsächlich der Fall.
Wir gucken uns die Klausel nochmal an. Es gibt nur
ein eins den Rekursiven Aufruf hier drin, das ist der.
Und wir müssen zwei Fälle unterscheiden. Die aufsteigende Rekursion,
wo ich also vom Rekursionsabschluss anfange zu berechnen,
das ist im Falle der Fakultät passiert.
Oder der zweite Fall ist die Endrekursion,
wo ich nur beim Abstieg berechne und dann unten
das Ergebnis kenne
Das ist der Fall, den wir bei den PA-Notzahlen kennengelernt haben.
Das wird auch als absteigende Rekursion oder repetitive Rekursion
oder im Englischen als Tale-Recursion bezeichnet. Und Till Recursion,
für alle die, die schon mal funktionale Programmierung gemacht haben,
wissen, dass das gewaltige Vorteile mit sich bringt.
Also erstmal der Fall der Induktion, der Abstieg wird dort nur gemacht,
um den Rekursionsabschluss zu finden und von dort aus werden dann alle wesentlichen Berechnungen beim Rekursiven-Aufstieg gemacht.
Und das erlaubt oftmals richtungsunabhängiger, aber relativ ineffiziente Implementierungen,
weil dem ich in vielen Fällen ich dort Generate und Test betreiben muss,
dass ich erstmal alles Zahlen generiere in der Hoffnung,
dass die richtige dann irgendwann dabei vorbeikommt.
Also das gilt nur für bestimmte Instanzierungsvarianten. Bei der Endrekussion
mache ich die ganze Berechnung nur auf dem Absteigenden erst der Rekursion
und habe beim Rekursionsabschluss bereits das Ergebnis und der Aufstieg,
die einzige und alleine dazu, um das Ergebnis wieder nach draußen zu übermitteln.
Also von ganz innen in der Rekursion nach außen auf
die äußerste Schale. Das brauche ich zum Beispiel,
wenn der Induktionsanfang oder der Rekursionsabschluss vor der Berechnung überhaupt noch
vollkommen unbekannt ist, dann kann ich ihn nicht suchen,
dann muss ich erstmal rechnen,
um ihn überhaupt zu finden
Und wenn denn diese Endrikusion vorliegt,
und das ist der wesentliche Vorteil davon,
dann kann ich das Ganze auch immer iterativ implementieren und muss
mir die Zwischenergebnisse gar nicht mehr merken, die zwischen Ergebnisse der Berechnung.
Und die Zwischenergebnisse der Berechnung stehen auf dem Kellerspeicher für die ganzen Protekturaufrufe, also Prädikataufrufe.
Und die muss ich gar nicht mehr anlegen. Das heißt,
hier, und da können wir uns zahlreiche Beispiele angucken,
wenn ich eine solche Endrikusive Implementierung habe,
kann ich üblicherweise auch sehr große Probleme noch behandeln,
während die nicht entrikusive Variante in den allermeisten Fällen dann
relativ bald zu einem Kellerspeicherüberlauf führt
Das Schöne ist, dass man Freunde der Funktionalen Programmierung wissen das,
dass man Nicht-Endrekussionen immer in Endrekussionen umwandeln kann. Der Trick,
den man dafür braucht und der ist in der funktionalen Programmierung
genau der gleiche wie in der relationalen Programmierung, ist Augmentation.
Augmentation heißt, ich brauche ein zusätzliches Argument.
Und auf diesem zusätzlichen Argument verwalte ich ein Zwischenergebnisspeicher.
Auf die Art und Weise kann ich auf dem Zwischenergebnisspeicher
Werte zusammentragen und deshalb heißt das Ding auch Akkumulator,
weil ich dort dass das Zwischenergebnis immer weiter akkumuliere,
bis es schließlich vollständig zur Verfügung steht.
Und die Analogie zu der funktionalen Programmierung, die ich jetzt
hier immer betont habe, die kommt durchaus nicht zufällig,
sondern wir reden jetzt hier über arathmetische Berechnungen.
Da sollen wir uns nicht wundern,
dass dort die Ähnlichkeit besonders groß und besonders ausgeprägt ist.
Also wir hatten diese aufsteigende Rekusive Definition,
die eben nicht endrekusiv ist,
weil ich zuerst die Fakultät für N-1 berechne und anschließend nochmal
das Ergebnis weiterverarbeiten muss und damit ich das machen kann,
muss ich
dieses Zwischenergebnis irgendwo gespeichert haben
Und dafür ist der Keller Speicherdaten. Wenn ich das vermeiden kann,
dann kann ich auf das Speichern der Zwischenergebnisse verzichten.
Und dann komme ich zu erheblich effizienteren Implementierungen.
Das ist also die Definition, die wir da hatten.
Und jetzt wandeln wir sie tatsächlich um, in eine absteigende,
rekrusive Definition, indem wir jetzt sagen, okay,
wir nehmen ein zusätzliches Argument hinzu.
Das heißt hier A für Akkumulator. Das initialisieren wir mit einer Eins. Am Anfang,
und das macht Sinn für die Fakultät,
die Fakultät berechnet ja immer durch Multiplikationen,
die Eins ist das neutrale Element der Multiplikation,
also initialisieren wir das mit Eins
Der Abbruch erfolgt, wenn denn unser Resultat, nein,
der Abruf erfolgt, wenn wir unseren Faktor,
den wir ja immer verringern, auf null verringert haben,
dann können wir ihn nicht weiter verringern.
Und dann enthält der Akkumulator unser Resultat. Wichtiger ist,
wie kommt es eigentlich zustande? Wir rufen also jetzt die Prozedur
immer mit einem umeinsverringerten N auf, mit auf dem Akkumulator,
mit dem Produkt aus altem Akkumulator wert und der aktuellen Zahl N.
Und hier hinten wird das Ergebnis übergeben
Und genau das übertragen wir jetzt in diese Definition,
indem wir eben sagen, wir testen erst mal wieder,
auch dass das Ganze nur für natürliche Zahlen passiert.
Wir verringern N um eins. Wir multiplizieren unseren Akkumulatorwert,
den wir hier bekommen, mit dem aktuellen N und kriegen damit einen neuen Akkumulatorwert.
Und mit diesem neuen Akkumulatorwert rufen wir das Ganze rekursiv auf.
Und jetzt wird also der alte Akkumulatorwert,
der hier mit dem neuen multipliziert, hier hineingeschickt,
wo er mit der nächsten Zahl multipliziert wird und wieder mit
der nächsten Zahl und wieder mit der nächsten Zahl.
Und wenn ich das N durch Subtraktion irgendwann mal auf null
verringert habe, dann enthält der Akkumulator genau die Wertebelegung,
die ich brauche als Berechnungsergebnis
So, was ist der Unterschied zwischen diesen beiden Prädikatsdefinitionen?
Hier sind sie noch mal gegenübergestellt. Der wesentliche Unterschied ist hier unten zu sehen.
Hier berechne ich erst die Fakultät für den um eins verringerten N-Wert.
Und danach muss ich noch mal was machen.
Hier berechne ich erst alle Hilfsgrößen, die ich brauche,
also den um eins verringerten Wert WN, den neuen Akkumulatorwert.
Das brechne ich zuerst und als allerletztes wird die Fakultät daraus.
Einfach der rekursive Aufruf durchgeführt, um den nächsten Rekursionsschritt zu machen.
Und das heißt, hierfür Da hier, danach keine Berechnungsoperation mehr
durchgeführt werden, brauche ich auch keine Zwischenergebnisse zu speichern.
Also Endrekussionen erkenne ich immer daran, dass der rekursive Aufruf,
der letzte Aufruf in der Klausel ist.
Wenn der rekursive Aufruf von Fakt 1 hier,
der letzte Aufruf in der Klausel ist, dann ist das eine Endrekursive Definition.
Mit all den Vorteilen, die ich dann habe,
dass Prolog-System erkennt Endrekursionen sofort und führt das dann entsprechend optimiert aus.
Das ist ein Service, den jedes Prolog-System eigentlich
mit sich bringt
Gut, was ich natürlich den Preis,
den ich dafür zahlen muss, ist,
dass ich diesen Rapper da außen drum rum brauche,
der jetzt die Abbildung dieser Definition auf die ursprüngliche,
auf die ursprüngliche Signatur bewirkt,
dass sie also wirklich das Ganze nur aufrufe mit einem N und das Ergebnis der Vokultät bekomme.
Und hier bei der Gelegenheit kann ich eben den Akkumulator mit eins initialisieren,
damit er anfängt von eins die Ergebnisse aufzumultiplizieren. Gut,
damit haben wir mal einen Fall gesehen, einen sehr einfachen Fall,
wo wir eine Endrekusive Definition und eine Nicht-Endrekusive Definition gegenübergestellt haben,
wo wir dann vergleichen können bezüglich der Berechnungseigenschaften, welches eigentlich attraktiver.
Und wir werden in vielen Fällen dann eben finden,
dass wir hier mit erheblichen Effizienzvorteilen rechnen können.
Hier sind nochmal die Rekusionsschemata gegenübergestellt, damit man tatsächlich sieht, was passiert.
Das hatten wir schon gesehen. Wir verringern immer das N um eins,
aber wir bauen hier nur eine Chore-Referenzkette auf und berechnen eigentlich nichts in der Pakulität.
Alles, was wir uns merken, ist,
dass das X, was wir hier mal wissen wollen,
X1, das ist dieses hier, mal vier ist.
Und dass dieses X1, das hier mal X-2 mal drei ist,
aber wir können dieses Teil noch nicht ausrechnen,
weil wir X2 noch nicht kennen.
Und auf der rechten Seite der aratmetischen Auswertung und Umgebung müssen wir vollständig extraziiert haben.
Das heißt, hier, diese Berechnung können wir noch nicht anstoßen.
Wir müssen uns auf dem Kellerspeicher merken, das muss in Zukunft
noch passieren, dass wir das hier tatsächlich dann ausrechnen.
Und erst wenn wir hier unten angekommen sind,
dann haben wir plötzlich einen Wert für X4,
Und können diesen Wert für X4 mit Eins multiplizieren und kriegen den Wert für X3 raus.
Und jetzt haben wir den Wert für X3 und können den
Wert für X2 berechnen. Und wenn wir den haben,
können wir den Wert von X2 benutzen, um X1 zu berechnen.
Und schließlich können wir X1 benutzen, um X zu berechnen.
Und das ist genau das, was hier beim rekrusiven Aufstieg passiert,
dass jetzt erst, nachdem wir hier unten waren,
wir das Ergebnis sukzessive aufbauen können.
Und im Gegensatz dazu hier im Mehrendrekrusiven Definition sehen wir,
dass auf dem Akkumulator genau diese Berechnung jetzt in einer anderen Reihenfolge,
allerdings durchgeführt wird. Wir könnten die richtige Reihenfolge auch wieder.
Da wird das Programm aber nur ein kleines bisschen komplizierter.
Also wir haben von N auf null runtergezählt.
Wir könnten genauso gut von null bis zu Ende zählen.
Das ist äquivalent. Und deshalb kommt also hier heraus,
dass wir erst die Eins mit der Firma multiplizieren,
dann mit der Drei, dann mit der Zwei.
Und dann haben wir das Ergebnis da und sind unten angekommen.
Der Rekursionsabschluss sorgt jetzt nur noch dafür,
dass der Wert von dem Akkumulator auf die Ergebnisposition übergeben wird.
Und wir haben schon beim Abstieg diese Chorrepferenzkette aufgebaut,
dass X gleich X1 ist, gleich X2 ist,
gleich X3 ist, gleich X4 ist.
Das heißt, wenn wir hier unten angekommen sind und
den Wert für X4 kennen,
kennen wir auch
den Wert für
Der gesamte regulsive Aufstieg ist unnötig und muss gar nicht mehr durchgeführt werden.
Wir können ihn einfach weglassen. Und da sehen wir den enormen Unterschied,
diesen ganzen Zwischenergebnisse hier, die müssen nicht mehr abgespeichert werden
und das belastet das System gerade für große N natürlich gewaltig.
Sondern wir gehen einfach nur einmal nach unten, kennen die Zahl
und sind fertig. Das ist der große Vorteil von End-Trikulsion.
Gut, damit sind wir an einem schönen Punkt angekommen.
Wir haben ein einfaches Beispiel gesehen,
wie wir jetzt mithilfe der Auswertungsumgebung tatsächlich arithmetische Berechnungen durchführen können.
Wir haben den Unterschied gesehen, zwischen Endtrikot 7 und
nicht intriklusiven Prädikatsdefinitionen. Wir gucken uns weitere Beispiele an.
Und ein zentrales Thema auch der Übungen wird dann immer sein,
dass wir diese beiden Implementationsvarianten gegenüberstellen.
Und dann bewerten hinsichtlich ihrer Berechnungseigenschaften. Also ist die Endtrick,
ist die, welche vor- und welche Nachteile sind mit den beiden Implementierungsvarianten dann tatsächlich verbunden?
Gut. Und das soll es für heute gewesen sein.
Ich wünsche Ihnen ein schönes Wochenende und bis nächste Woche.