So, einen schönen guten Tag, allerseits,
wir haben ja unsere Tradition,
dass wir die Sache immer mit einem Cartoon zur Rekursion eröffnen.
Die Frage ist heute, wo ist die Rekursion?
Sieht keiner
die Rekursion
Dann haben wir wenigstens eins gemeinsam, da ist keine Rekursion.
Das ist ein kleiner Gag zum,
aus Anlass der aktuellen Feiertage, die kommen,
da ich das heute in etwas entspannter Atmosphäre zelebrieren möchte,
habe ich Ihnen hier auch was mitgebracht.
Lassen Sie das mal bitte kreisen und bedienen Sie sich.
Ups. So, in solcher Art mit Bestechung versorgt,
können wir wieder in die Inhalte einsteigen.
Wir werden uns heute mit Prädikaten höhere Ordnung befassen.
Prädikate höhere Ordnung sind uns an einzelnen Stellen schon begegnet.
Wir gucken uns die jetzt nochmal in einem systematischen Kontext an.
Und das Ziel des Ganzen wird sein,
dass wir uns über mehrere Stufen hinweg der Metaprogrammierung nähern
Metaprogrammierung heißt also der Zustand, dass Programme, lass es Kurs sich anhören.
Dass wir Programme haben, die andere Programme nehmen,
deren Verhalten analysieren und die entsprechende gewünschte Funktionalität auslösen.
Wir haben dann also Basisebenenprogramme, das ist eine Definition von
Last, wie wir sie immer verwendet haben.
Und wir setzen darüber einen Metainterpreter und anstatt dieses Programm jetzt
direkt in das Prolog-System zu schicken, wird dieser Metainterpreter aktiviert,
der dieses Programm startet und dann Abarbeitung beeinflusst.
Und auf die Art und Weise werden wir eine ganze Reihe interessanter Effekte erzeugen.
Die laufen alle darauf hinaus, dass wir jetzt anfangen,
die Semantik des Prolog-Interpreters selbst in die Hand zu nehmen.
Dass wir uns nicht mehr auf die eingebaute Semantik verlassen,
sondern dass wir selbst festlegen, wie Klauseln abgearbeitet werden sollen.
Das macht sich in so einem interpretativen Kontext natürlich besonders gut.
Versuchen Sie das Gleiche mal in einer imperativen Programmiersprache wie Java
zu machen. Da werden Sie schnell an die Grenzen stößen,
erstoßen, also im Rahmen der funktionalen Programmierung und der Logikprogrammierung
machen sich solche Metaprogrammierungsansätze besonders elegant
Also, was sind Prädikate höherer Ordnung?
Das sind im Prinzip zwei verschiedene Mechanismen. Einerseits,
dass ich Prädikate habe, die wieder Prädikatsaufrufe als Argumentbelegungen akzeptieren.
Ich übergebe also einem Prädikat ein Ziel und möchte,
dass dieses Ziel von dem Prädikat aktiviert wird.
Da haben wir schon Beispiele für gesehen.
Kann sich
jemand erinnern
Genau, da ist das aufgetreten,
das ist hier der Prikatsaufruf im Prinzip gewesen.
Wir haben es noch an anderen Stellen gesehen.
Das ist unser Find All. Find All nimmt ein Ziel,
aktiviert dieses Ziel und sammelt die Ergebnisse der Abarbeitung dieses Ziels auf.
Das ist ein Fall von Prädikat höherer Ordnung. Wir kommen,
wo das hier reingehört, da kommen wir noch hinzu,
das ist nämlich genau diese Ermittlung von aggregierten Berechnungsergebnissen
Das ist also die eine Sicht. Wir geben eine Prädikatsdefinition
sozusagen in die Abarbeitung hinein. Und wir sagen, welches Ziel
aktiviert werden soll. Die andere Sorte von Prädikaten höherer Ordnung
sind all die Prädikatsdefinitionen, die selbst wieder Prädikate erzeugen.
Das geht natürlich auch. Wir werden das hier nicht behandeln.
Das ist also der gesamte Bereich der Programmtransformation. Wir haben
jetzt schon gesehen an Einzelbeispielen, dass man Prolog-Programme sehr,
sehr schön transformieren kann, durch Augmentation,
indem man Argumente hinzufügt oder wegstreicht und dadurch andere Prädikatsdefinitionen daraus ableiten kann.
Man kann auch eine ganze Reihe im Reich der Optimierung der Abarbeitung machen,
Und das läuft immer darauf hinaus, ich habe eine Prädikatsdefinition gegeben,
ich erzeuge daraus eine neue und trage die in die Datenbasis ein,
damit sie dann zur Abarbeitung zur Verfügung steht.
Das Besondere der Logikprogrammierung ist hier allerdings,
dass die Prädikatsdefinition ja immer klauselweise erfolgt. Wenn ich also
ein Assert verwende, dann wird immer Klausel für Klausel eingetragen.
Das ist grundsätzlich anders als in der funktionalen Programmierung,
das werden wir noch sehen.
In der funktionalen Programmierung kann ich eben eine Funktion neu erzeugen
und die gesamte Funktion sofort wieder in die Abarbeitung hineinstecken.
Hier kann ich immer nur einzelne Klauseln erzeugen und muss dafür sorgen,
dass die Prädikatsdefinitionen dann vollständig ist,
indem ich alle Klauseln, die benötigt werden,
tatsächlich erzeuge
Das ist also ein kleiner Unterschied zwischen funktionaler und Logikbranierung an dieser Stelle. Ja,
den einfachsten Prädikatsaufruf haben wir schon gesehen,
das ist dieses einstellige Prädikat Call,
dass wir also einen Term haben,
der als Ziel interpretiert werden soll und dann mit Call wird die Suche gestartet,
nach möglichen Vayablen, Instanzierungen für die hier angegebene Struktur.
Ja, insbesondere sinnvoll, wenn man eben dynamisch erzeugte Prädikate
und Prädikatsaufrufe hat, man baut sich ein Prädikat zusammen,
man möchte es dann abarbeiten und das erfolgt dann mit dem
Es kann auch passieren zur verzögerten Aktivierung von Zielen.
Ich baue erstmal ein Ziel auf, möchte es aber noch nicht sofort abarbeiten,
sondern erstmal aufsammeln, vielleicht eine ganze Sammlung von Zielen aufbauen.
Und dann entscheiden, welche dieser Ziele möchte ich tatsächlich abarbeiten.
Und das ist dann genau der Bereich der Meta-Programmierung,
wo ich selbst in die Hand nehme, wie die Verarbeitung weitergehen soll.
Also beispielsweise,
wo ich die klassische Tiefe zuerst suche des Prolog-Systems durch eine Breitensuche ersetze,
indem ich alle Ziele, die ich abarbeiten möchte,
erstmal in einer Liste sammle und die dann anders abarbeiten würde,
als es
die Tiefesuche macht
Ohne Meta-Programmierung haben wir die Idee ja schon gesehen,
als wir unseren endlichen Automaten von Tiefensuche auf breiten Suche umgestellt haben.
Und das geht natürlich genauso,
wie ich das für die Suche des im endlichen Automaten mache,
geht das genauso für die Suche im Suchraum des Prolog-Systems zu realisieren.
Ja, es gibt die sogenannte Meta-Call-Facility,
die sorgt dafür, dass wenn ich einfach dieses,
eine Variable im Körper einer Klausel angebe und diese Variable an eine Struktur gebunden ist,
dann wird diese Struktur als Aufruf in das Prolog-System geschickt.
Das ist also die einfachste Form,
ein Meta-Interpreter zu erzeugen
Also in diesem Falle,
wenn ich das hier definiert habe und das dann aufrufe mit einem Ziel,
wird eben genau dieses Ziel hier aktiviert und über die Suche abgearbeitet.
Und ich kriege klassisch wie sonst auch immer die Ergebnisse ausgegeben,
die dort ermittelt werden. Wir können uns an der Stelle angucken,
dass die Definitionen der Diszipunktionen eigentlich gar nichts Neues ist.
Wenn wir also Diszipunktionen mit diesem Operator Semikole definieren wollen,
dann können wir das definieren zu X oder Y,
es definiert als X oder alternatives XY definiert
Da könnte man natürlich anonyme Variablen vorsehen für das Y,
für das X, damit man die entsprechenden Warnungen vermeidet.
Aber damit wird deutlich, was eine Diskussion eigentlich ist.
Das ist nichts anderes als die Angabe von der beiden des
jungte Altseparate Klauseln. Das ist vollkommen äquivalent, semantisch.
Und tatsächlich ist sie das junktionenintern auch so definiert.
Negation ist ein bisschen interessanter. Sie haben ja auch schon
zur Genüge verwendet und wir können jetzt sehen,
wie die Negation tatsächlich implementiert wird.
Wir haben ein einstelliges Prädikat Not Plus oder früher
war es eben Not
Und wir geben dem Ganzen ein Ziel vor. Dieses Ziel wird
über die Meta-Call-facility aufgerufen. Und wenn das Ganze positiv war,
wird danach ein Cut gesetzt. Ja, Cut besorgt dafür,
dass die alternative Klausel hier nicht mehr sichtbar ist.
Und das Ganze lässt man scheitern. Also abarbeiten des Zieles,
falls positiv scheitern lassen, dann wird der Wahrheitswert umgedreht.
Und der Cut sorgt dafür, dass diese Klausel nicht mehr
sichtbar ist. Falls dieses Ziel hier scheitert,
dann geht es über das ganz normale Backtracking in die
zweite Klausel und die zweite Klausel ist erfolgreich,
egal was hier als Wert übergeben wird
Und damit haben wir die Definition der Negation und wir sehen eigentlich,
dass die in der Negation nichts anderes ist,
als Staaten der Suche umdrehen des Wahrheitswertes und einen Cut,
der dort drin versteckt ist, der dafür sorgt,
dass die beiden Wahrheitswerte wirklich als Alternativen auftreten und nicht alternative Berechnungsergebnisse hier erzeugt werden.
Und wenn man das also jetzt mit so einem Ziel hier aufruft,
unifiziert eins mit zwei, dann wird dieses Ziel hier aufgerufen,
das scheitert und dann ist diese Klausel erfolgreich.
Im anderen Fall, wenn das Ziel erfolgreich ist,
hier wird eben die alternative Klausel weggeschnitten und
das Ganze kommt mit einem False zurück,
scheitert also
Ja. Also, diese, diese Metaprogrammierung erlaubt uns jetzt
hinter die Kulissen zu gucken oder mit anderen Worten,
die Dinge zu definieren, die wir bisher eigentlich nur passiv benutzt haben.
Wir sehen hier auch das Problem Delegation,
das haben wir auch schon diskutiert, dass die Negation,
die Argumente nicht instanziert. Ich kann also niemals eine Fragestellung,
Frage stellen nach den Bedingungen, die ein Ziel scheitern lassen,
sondern ich werde immer nur die Antwort bekommen, es ist gescheitert.
Und deshalb, das hatten wir auch schon in der Übung,
doppelte Negation ändert dann das Systemverhalten, wenn ich also so etwas aufrufe,
gib mir denn das letzte Element dieser Liste,
kriege ich das gewünschte Ergebnis, wenn ich das Ganze negiere,
Dann kriege ich hier ein Falls raus,
weil es natürlich ein letztes Element dieser Liste gibt.
Das Ganze ist erfolgreich und die Negation sorgt dafür,
dass es umgedreht wird. Und wenn ich jetzt zweimal negiere,
dann kriege ich eben keine Variablenbindung für X mehr wie hier oben noch,
sondern nur, ja, ja, es gibt schon irgendwie eine Möglichkeit,
dass das Ganze erfolgreich ist.
Aber ich weiß nicht mehr, auf welcher Basis,
das ist hier tatsächlich in dem Schritt bereits verloren gegangen.
Hier wird keiner, unter dem Not wird keine Variablenbindung nach
draußen gegeben und deshalb kann ich auch bei der doppelten Negation
dann nicht erwarten,
dass hier irgendeine sinnvolle Variablenbindung erzeugt wird
Aber immerhin kriege ich heraus, das Ganze ist wahr,
ja, das, soweit reicht es noch, aber nicht die Bedingungen, die es wahrgemacht haben. Also,
Negation prüft nur das Scheitern des Ziels,
deshalb wird es auch als Niggation Esfailure bezeichnet, die Voraussetzung,
dass sie das machen kann, ist immer, dass ich annehme,
dass meine Welt abgeschlossen ist, das heißt also,
nur das, was in der Datenbank drinsteht, gilt,
ja, Dinge, die aus der Datenbank nicht ableitbar sind,
sind fehlerhaft oder falsch, ja,
da kriege ich den Wert False raus an dieser Stelle.
Und wir haben auch hier wieder einen Fall einer nicht monotonen Logik vorliegen,
und zwar ist das der einfachste Fall, der nicht monotonen Logik.
Wir hatten ja bereits die,
dass die fortschließen im Zusammenhang mit dem Cut gesehen und wir kein Wunder,
die Negation ist nicht viel mehr als dieser Cut hier.
Das heißt, auch hier müssen wir wieder damit rechnen,
dass wir keine monotonen Schlüsse mehr ziehen können, sondern nicht Monotonie hineinbekommen.
Das ist immer bei der Negation so. Und wir können
uns das wieder angucken. Wir haben ein Aktionensystem aus A von X,
kann ich B von X ableiten oder umgekehrt aus B von X kann ich A von X ableiten.
Und ich weiß, dass B von A gilt.
Wenn ich jetzt also nach A von A frage,
dann wird B von A aktiviert, der Fakt liefert,
ja, das passt und deshalb kriege ich ein True heraus.
Wenn ich nach A von B frage,
dann kommt ein False heraus,
weil über B von B
hier nichts gesagt ist
Das kann ich nicht ableiten mit diesem Aktionensystem.
Wenn ich jetzt diesen Fakt mit hinzunehme, B von B
kriege ich auch die entsprechende Schlussfolgerung aus dem System heraus,
jetzt wird das hier also wahr.
Hinzunehmend eines zusätzlichen Axioms hat auch unsere Menge an Theoremen,
die wir daraus ableiten können, größer gemacht.
Das Monotonie fordert nur, dass sie nicht kleiner wird.
Hier ist sie tatsächlich größer geworden,
aber sie ist nicht kleiner,
ist ein Spezialfall einfach
nicht kleiner geworden
Das heißt also, es ist uns keine Ableitung verloren gegangen.
Das ist das Wesentliche bei Monotonie. Im Gegenteil,
wenn wir jetzt eine Nicht-Monotonie einbauen,
also hier in Form dieses Negationsoperators, wir haben das gleiche Aktionensystem,
nur dass jetzt hier die Negation drin ist,
dann kriegen wir natürlich für A von A Falls heraus,
weil eben, wenn A von A gilt,
dann muss nicht B von B,
B von A gelten und B von A gilt aber und der Wahrheitswert und umgedreht und deshalb kommt ein Falls heraus.
Und er analog dazu, A von B es war,
weil eben B von B
nicht in der Faktensammlung drinsteht
Wenn wir jetzt B von B hier in die Faktensammlung mit hinzunehmen,
dann sehen wir, dass uns tatsächlich eine Ableitung verloren geht.
Denn hier war für A von B vorher das Ergebnis True
und jetzt ist es False geworden. Hier ist uns also
durch den Zunahme eines Axioms hier ein Theorien verloren gegangen.
Und das ist nicht Monotonie. Also ein Spezialfall der Nicht-Monnotonie. Gut,
es ist die, in diesem Sinne eben keine vollständige Negation,
die wir hier haben, eine vollständige Negation,
würde uns auch sagen müssen, warum ein Ziel gescheitert ist.
Das ist ja leider nicht möglich.
Das ist also als eine Billigvariante von Negation,
die wir
hier implementiert und verwendet haben
Nächster Abschnitt sind die Steuerstrukturen, Steuerstrukturen haben wir schon eine ganze Reihe kennengelernt. Wir haben Klauseln verwendet,
die ja im Prinzip Alternativen darstellen und wir können natürlich jederzeit Bedingungen in die Klauseln,
in den Körper einer Klausel hineinschreiben oder aber auch,
wir können den Kopf der Klausel so gestalten,
dass er Bedingungen dafür ausdrückt,
unter welchen Umständen die Klausel gewählt werden kann.
Das ist also nichts anderes als eine konditional,
was wir dort aufschreiben. Wir geben Bedingungen an,
unter denen eine Klausel gewählt werden kann und das System wählt
uns die Alternativen aus, die verträglich sind.
Der Unterschied zur klassischen konditionalen sollte aber auch klar sein,
bei einem klassischen Konditional wird eben ein Zweig ausgewählt.
Hier haben wir das immer modulo des Backtrackings.
Wenn mehrere Zweecke oder mehrere Guards positiv ausgewertet werden können,
dann werden eben auch mehrere Alternativen verfolgt von der Suche.
Die Rekussion ist auch eine Steuerstruktur. Es ist eine der
Basissteuerstrukturen, die wir haben, um Zyklen zu bilden.
Das haben wir in sehr, sehr vielen Fällen inzwischen benutzt,
indem wir über Listen rekursive Abarbeitung betreiben und damit im Prinzip ein Schleifenkonstrukt realisiert haben.
Auch der Cut ist eine Steuerstruktur, der uns nämlich sagt,
ob wir bestimmte Alternativen noch sehen wollen oder nicht sehen wollen.
Der Cut ist genau das, was also unsere Guarded Clauses
zu einem tatsächlichen Konditionalmacht oder machen kann, sagen wir es so.
Wir können durch den Cut sicherstellen, dass nur eine Lösung
tatsächlich genommen wird. Wir gucken uns jetzt tatsächlich noch
zusätzliche Konstrukte an und das sind die klassischen Konditionale,
dass FNLs etwa und die Iterationszyklen,
die wir in der Logikprogrammierung
zur Verfügung haben
Also, klassisches Konditional ist eben das If-Ven oder des If-Ven Els.
Und wir sind auch jetzt hier wie bei der Negation in
der Lage zu programmieren, wie die sich verhalten.
Also, die Syntax ist vereinbart, dass dieser Pfeil hier sagt,
das ist ein If-Ven. Und wir definieren diese Operator-Ausdruck,
das ist ein Operator hier mit einem Bedingungsteil und einem Aktionsteil,
definieren wir so, dass wir den Bedingungsteil auswerten,
also seinen Wahrheitswert ermitteln und gegebenenfalls auch vaiablen Bindungen ermitteln.
Wenn das erfolgreich war, setzen wir den Cut,
Und arbeiten auch den Teil rechts von dem Bedingungsfall ab.
Das heißt, das ist jetzt ein Ausdruck,
wo der Van-Zweig nur dann abgearbeitet wird,
wenn hier bei der Auswertung ein Truh herauskam.
Das hat jetzt aber eine ganz gravierende Konsequenz,
die dieses Konstrukt dramatisch unterscheidet von dem,
was wir aus der imperativen Programmierung her kennen.
Wenn nämlich dieses If hier scheitert,
dann scheitert das gesamte Konditional
Das ist nicht der Fall, wenn ich also in einer
emperativen Sprache irgendein If-Van hinschreibe, wenn das scheitert,
dann geht es sofort mit der nächsten Anweisung weiter.
Wenn ein solches konditional in einer Sequenz von Anweisungen im Körper einer Klausel auftaucht,
ist Feierabend, wenn das hier mal scheitern sollte.
Das ist ein ganz, ganz zentraler Unterschied in der Semantik,
der die Lösung dafür ist, man verwende am besten nie das If-Van alleine,
sondern immer nur im Konstrukt If-Van Else,
dann ist man auf der sicheren Seite.
Das If-Van Else ist einfach nur eine kleine Erweiterung.
Es gibt jetzt also hier
noch ein Alternativzweig
Pattern ist, ich habe hier eine Bedingung angegeben.
Dann habe ich hinter dem Pfeil wie bisher auch den Ventzweig,
dann kommt das Semikolon und dann kommt der Els Zweig.
Also hier Action, die Ven-Action und die Els Action.
Und der Semikolon ist ja tatsächlich bisher immer gewesen,
gib mir alternative Lösungen.
Auch die Release ab der Disjunktion ist ja so gewesen.
Also hier ist die Interpretation, wenn dieses nicht gilt,
dann muss dieses gelten. Also auch eine Form von Alternative
. Und insofern ist das Semikolon semantisch auch
genau richtig platziert
Ja, die Definition sagt dann also, ich habe hier im Körper,
äh, sorry, im Kopf der Klausel,
dieses Pattern mit dem Konditionalpfeil und dem Els-Operator.
Und das wird jetzt wieder die Bedingung getestet,
wenn die Bedingung positiv war, wird der Wen-Zweig abgearbeitet.
Und danach ist Feierabend, weil durch den Cut
hier die zweite Klausel nicht mehr sichtbar ist.
Das heißt also, nachdem dieser Wen-Zweig abgearbeitet worden ist,
ist die Bearbeitung zu Ende. Falls dieses IF scheitert,
und das ist jetzt der interessante Fall,
den wir hier oben nicht haben, der kommt jetzt hier aber dazu,
falls der scheitert, wird ohne irgendeine Bedingung,
der er als Zweig abgearbeitet
Allerdings und da wegschneiden, all Alternativen,
die möglicherweise sich aus dem IF ergeben haben.
Das IF wird durch diesen Katier und durch diesen natürlich auch eindeutig gemacht.
Das wird also nur ein einziges Ergebnis betrachtet,
wenn das IF einmal wahr geworden ist, dann wird der Wendtzweck abgearbeitet.
Und wenn das scheitert, dann wird eben der Erzzweig abgearbeitet.
Auf die Art und Weise können wir also,
dass ein solches Konditional definieren. Und wenn wir dies verwenden,
immer, auch dann, wenn wir keinen Els-Alternative haben,
dann schreiben wir hier eben einfach ein True rein und dann
ist gut
Und dann heißt es also, unter dieser Bedingung mache das,
andernfalls ist es wahr. Und dieses,
dann ist es wahr, sorgt genau dafür,
dass das nicht passiert, was hier oben passieren würde,
dass es nämlich nach dem Scheitern des Ifs nicht mehr zu den nächsten Klauseln weitergeht.
Das Truh würde dann dafür sorgen,
dass mit der nächsten Klausel im Körper der, nein,
mit dem nächsten Teilziel im Körper der Klausel weitergearbeitet wird.
Gut, auch hier sehen wir wieder,
dass die Semantik des If and Ls eigentlich nicht viel mehr ist,
als dieser versteckte Cut.
Also Negation und If and Als sind Mittel, um den Cut
zu verstecken. Und das geht gut einher mit dem,
was ich zum Cut gesagt habe, vor die Woche,
die Benutzung des Cuts ist hochgradig gefährlich.
Man übersieht in komplexen Programmen nicht mehr, was wirklich passiert.
Da muss man sehr, sehr genau die prozedurale Semantik verfolgen.
Und das ist üblicherweise schwierig. Und das Not und das If
Van Elts sind schöne Möglichkeiten, einen Cut zu benutzen,
ohne ihn selbst setzen zu müssen.
Und die Implementierung des If Van Elts und der Delegation
ist tatsächlich so, dass man sich dann
darauf verlassen kann
Stimmt auch mit der Intuition, die man hat, einigermaßen überein.
Der einzige Falle, die man beachten muss, ist,
dass If-Wen, aber da hilft am besten nicht benutzen.
Benutzen sie das If-Wen Else und dann ist man weitgehend auf
der sicheren Seite. Der Vollständigkeit halber noch der Softcut,
den gibt es auch. Wir hatten hier gesehen,
dass durch diesen Cut hier das If eindeutig gemacht wird.
Es wird nie nach einer Alternative für dieses If hier gefragt.
Und aus diesem, das ändert sich beim Softcut,
beim Softcut bleibt das If-Ziel Backtrackbar
Das ist hier mal gegenübergestellt. Hier oben ist das Klassische
If Van Else, was wir eben handelt haben.
Hier unten ist der Softcut. Das hat die gleiche Struktur
mit einem solchen Pfeil und der in der Disjunktion.
Allerdings ist hier der Stern noch dabei, um zu zeigen,
dass es eine besondere Form des If Van Else ist.
Wir nehmen eine ganz, ganz simple Datenbasis, die steht
hier und jetzt stellen wir die Anfragen an das Prolog-System.
Also falls A von X, dann soll Y
gleich A sein und andernfalls soll Y gleich B sein.
Und dann kriegen wir natürlich heraus,
das X wird an Eins gebunden und da das wahr ist,
wird Y an A gebunden und wir sind fertig
Das ist noch eine zweite Bindung für X bekommt, das sehen wir nicht.
Das wäre ja noch die zweite Möglichkeit über A von 2 gegeben.
Oder wenn wir das andersrum aufrufen, wenn wir A von 4
aufrufen, da finden wir hier in der Datenbank nichts.
Das scheitert also, also kommen wir in den Wendzweig
und der Zweig bindet das Y an B.
Und das kriegen wir auch als Ergebnis ausgegeben.
Das ist also klassisches Konditional, if and else.
Und jetzt kommt der Softcut. Jetzt bleibt, wird das
der IF-Zweig hier vorn, bleibt backtrackbar. Das heißt,
nachdem wir das erste Ergebnis bekommen haben, so wie hier oben auch,
X wird ein 1 gebunden und Y kriegt deshalb den Wert A.
Das ist also mit der ersten Klausel hier erzeugt. Wenn wir dann das Backtracking erzwingen,
dann kommt eine zweite Bindung für x gleich für x zustande,
nämlich x gleich 2 und wir landen natürlich wieder im Ventzweig,
das heißt Y wird dann A gebunden.
Also wir haben zwei Möglichkeiten, das wahrzumachen und wir kriegen
auch beide Möglichkeiten hier angegeben. Und im negativen Falle,
wenn wir hier A von 4 eingeben, na da ändert sich natürlich nichts,
das scheitert und deshalb landen wir im Ventzweig und diese vaiablen Bindung kriegen wir auch nach außen zu sehen.
Also der Unterschied ist hier, die Alternativen,
die konditional Teil auftauchen, die werden auch tatsächlich alle abgearbeitet.
Ich kenne keine, ich habe schon mehrfach drüber nachgedacht,
keine richtig sinnvolle Verwendung dafür, deshalb hier nur ein abstraktes Beispiel und das Ganze der Vollständigkeit halber.
Nächster Abschnitt sind Zyklenkonstrukte, wie gesagt, wir hatten schon die Rekursion,
als das zentrale Zyklenkonstrukt und das ist auch die Form von Zyklus,
die für die Logikprogrammierung einschlägig ist, insbesondere dann,
wenn wir uns im Kernbereich der Logik befinden,
also dort, wo wir Über die rekursive Datenstrukturen,
irgendwelche Verarbeitungsprozesse definieren wollen.
Hier haben wir jetzt sogenannt andere Zyklenkonstrukte, die erste Variante,
die wir uns angucken, sind Zyklen,
die dadurch gesteuert werden, dass ich einen Aufruf scheitern lasse.
Die Grundstruktur ist also immer die Gleiche. Ich habe hier
irgendein Teilziel in einer Klausel, was mir Alternativen erzeugt.
Bit Wien erzeugt der Alternativen, in dem natürliche Zahlen von
1 bis 1 erzeugt werden. Es ist hier sogar so,
dass mich die natürlichen Zahlen überhaupt nicht interessieren.
Es ist einfach nur ein Aufruf, der mal von 1
bis 1 zählt.
Und zwar über die Alternativen hinweg
Und der auf die Art und Weise
nichts anderes macht als genau N-Entscheidungspunkte zu erzeugen.
Und für jeden dieser Entscheidungspunkte wird auf der Konsole,
das ist ein Ausgabebefehl, ein Leerzeichen ausgegeben.
Kann ich also verwenden, um eine Einrückung auf der Konsole zu erzeugen.
Also rücke mir das ganze Endzeichen nach rechts ein,
dann wird eben hier bis N gezählt und für jedes Mal
zählen wird ein Leerzeichen auf die Konsole geschrieben.
Und dann lasse ich das Ganze scheitern und das Scheitern führt dazu,
dass das Backtracking aktiviert wird.
Jetzt muss nämlich das System ins Backtracking gehen und gucken,
wo bei diesen Sachen
hier gibt es noch Alternativen
Das Putt erzeugt keine Alternativen. Input, Output ist eindeutig
. Da gibt es nur ein Ergebnis. Das mit Wien
aber erzeugt eine Entscheidung, ein Entscheidungspunkt. Das heißt,
jetzt wird die zweite Alternative von dem Bett Wien abgearbeitet.
Es wird wieder ein Leerzeichen ausgegeben. Das Ganze scheitert wieder
. Damit wird die nächste Alternative von Bett Wien aktiviert.
Und dieses Schema zwischen einem Prädikatsaufruf, der alternative Entscheidungspunkte erzeugt und dem Ganzen,
das Ganze dann scheitern lassen, das ist die Grundidee dieser fehlergesteuerten Zyklen.
Und das kann ich jetzt, also in verschiedenen Kontexten machen,
hier habe ich eben das Bett Wien verwendet.
Ich könnte genauso gut auch eine Liste verwenden, indem ich hier sage,
ja mache für alle Elemente dieser Liste, irgendetwas.
In diesem Falle also schreibt sie auf die Konsole,
machen Zeilenvorschub, dann scheitert das Ganze,
es kommt das nächste Element der Liste heraus.
Also eine schöne Verwendungsweise des Member-Prädikats als Generator von Alternativen.
Nicht als Testoperation ist ein Element in deiner Liste drin,
sondern sage mir, welche Elemente sind in dieser Liste drin und dann mache irgendetwas damit.
Und über das Fail wird dann eben hier der Zyklus hervorgerufen,
der so lange funktioniert, bis das Ganze,
bis die Liste abgearbeitet ist.
Bis hier keine weiteren Entscheidungspunkte mehr zur Verfügung stehen und
dann bricht natürlich das Backtracking ab.
Genau wie hier oben, wenn das N erreicht ist,
dann ist Feierabend
Und ich kann das Ganze natürlich genauso über der Datenbank machen,
wenn denn in meiner Datenbank Alternativen sind,
also beispielsweise hier diese Mitarbeitertabelle,
die wir uns schon mehrfach angeguckt haben,
da stehen eben mehrere Mitarbeiter drin,
dann kann ich eben auch den Aufruf für diese Faktendatenbank als Prädikatsaufruf,
neben der mir Entscheidungspunkte erzeugt.
Und dann kriege ich eben einen Mitarbeiter nach dem anderen,
ich mache damit irgendetwas und ich lasse das Ganze scheitern um den Zyklus, so schließen.
Und die Reaktion ist dann eben, wenn ich das aufrufe,
kriege ich der Reihe nach, die Namen ausgegeben hier
Eine Beobachtung ist, bei all diesen Sachen so,
das Ganze scheitert im Endeffekt immer.
Denn entweder es kommt hier zu dem Fail und geht
ins Backtracking oder aber dieser Aufruf hier scheitert bereits.
Wenn nämlich dann, wenn keine weitere Alternative mehr existiert.
Das heißt, der Aufruf einer solchen Schleife bringt immer einen Fehl zurück.
Das ist möglicherweise in irgendwelchen Anwendungen ein Schönheitsfehler. Und deshalb
muss man sich überlegen, was kann man dagegen tun?
Und die Reparatur ist natürlich ganz simpel. Ich habe hier also
diese fehlergesteuerte Schleife hier in diesem Fall über das Member-Prädikat.
Und wenn das Ganze scheitert, weil keine Elemente mehr in der Liste drin sind,
dann muss ich eben noch eine Alternative bereitstellen, die unter Ohne irgendeine Bedingung einfach True sagt.
Und dann scheitert eben die erste Klausel,
es geht über das Backtracking in die zweite Klausel,
die zweite Klausel sagt True und dann kann ich diese Schleife,
die ich dort habe, auch in einen größeren Kontext einbinden,
wo jetzt wieder mehrere Prädikatsaufrufe hintereinander stehen und kann sicher sein,
dass es mit dem nächsten Prädikatsaufruf weitergeht.
Dafür sorgt mir hier diese Alternative,
die ich hier angebe, dass in dem Falle,
dass der Zyklus erfolgreich abgearbeitet wurde,
auch tatsächlich im Endeffekt ein Wahrheitswert True rauskommt und nicht
das Ganze mit dem Wahrheitswert Fall zu Ende geht
Dass Prolog-System stellt auch einen Iterator zur Verfügung, das ist das For-Or,
wie der Name schon sagt, es ist ein Altquantor sozusagen.
Also ich mache eine Iteration über alle Alternativen,
die ich mit einem Prädikatsaufruf habe und tue dafür irgendetwas.
Also das Ganze besteht immer aus zwei Teilen, das habe ich
hier schlecht eingerückt. Das For-All hat immer generierenden Teil,
der die Alternativen erzeugt und einen Aktionsteil,
der dann sagt, was mit den Alternativen passieren soll.
Also unsere Top-Funktion können wir genauso gut dadurch schreiben,
indem wir sagen, für alle Aufrufe von Between zwischen 1 und N,
mache dieses Putt-Leerzeichen auf die Konsole.
Und da sieht man, das ist dann ziemlich äquivalent zu einer Vorschleife,
das kann man auch über Kollektionen machen, dass man eben sagt,
für alle Elemente einer Liste tue irgendetwas mit Ihnen.
Und das ist intern auch nichts viel anderes als eine fehlergesteuerte Schleife.
Aber es sieht nach außen hin ähnlicher der mathematischen Notation aus,
wo ich eben so einen Algvantor einsetze oder eben einem Zyklenkonstrukt.
Mache irgendetwas für alle Alternativen, die durch einen Präkatsaufruf geliefert werden.
Es gibt auch eine unbeschränkte Iteration, also eine, die nicht abbricht.
Alle die, die wir bis jetzt hatten, brechen ja irgendwann ab.
Das mit Wien hat irgendwann mal alle Zahlen abgeliefert,
danach gibt es keine Entscheidungspunkte mehr.
Das Member ist irgendwann mal am Listenende angekommen,
wenn die Liste nicht zyklisch ist,
ist damit die Abarbeitung
auch beendet
Was hatten wir noch? Die Datenbank ist immer endlich.
Also wenn es keine alternativen Klauseln mehr gibt, ist auch der Zyklusabbruch erreicht.
Wir können aber eben auch Schleifen machen, die unbeschränkt laufen.
Oder wo zumindest durch das Programm nicht vorgegeben ist,
wann der letzte Zyklus erfolgt. Und dafür gibt es
ein Prädikat, das ist nullstellig, das heißt Repeat.
Repeat tut nichts. Repeat tut nichts, es gelingt immer
. Und die einzige Effekt, den Repeat auslöst,
ist,
es erzeugt einen Entscheidungspunkt
Und auf die Art und Weise kann ich jetzt über das Backtracking eben diese Entscheidungspunkte abarbeiten.
Also jedes Mal, wenn es abgearbeitet wird, wird ein Entscheidungspunkt erzeugt.
Und das heißt, das System kennt eine Alternative.
Und in dieser Alternative kann es über das Backtracking einsteigen.
Also, was mach mal hier? Wir haben hier
ein Leseschreibzyklus und das ist ein Schema,
was sie gut und gerne verwenden können,
wenn es an die Weihnachtsaufgabe mit dem Chatbot geht.
Das macht nichts anderes, als dass es ein Satz,
nein, das ist eine Eingabe liest und diese Eingabe auf
die Konsole wieder ausschreibt
Dieses Ausschreiben auf die Konsole ist hier so gemacht, dass es zwei Fälle unterscheidet.
Entweder ich habe noch nicht den letzten Input erreicht,
dann schreibe ich das eben auf die Konsole und lasse das Ganze scheitern.
Und das Scheitern sorgt dafür, dass dann hier nach Alternativen gesucht wird.
Das Ried hat keine Alternativen, alle Input, Output-Prädikate sind eindeutig.
Aber das Repeat hat natürlich immer einen Entscheidungspunkt zur Verfügung.
Also geht es noch hier oben, nimmt diesen Entscheidungspunkt,
liest den nächsten Satz, ein Ruf das Echo wieder auf.
Das Echo prüft als erstes, ist es das vereinbarte Ende-Kennzeichen.
Wir können irgendetwas vereinbaren, zum Beispiel End. Wenn es
das vereinbarte Ende-Kennzeichen ist, was ich hier eingelesen habe,
dann Er setzt einen Cut, der Cut sorgt dafür,
dass jetzt die zweite Klausel verschwindet und die Verarbeitung ist beendet.
Also hier habe ich jetzt durch den Input gesteuert, wann
der Zyklus abgebrochen wird. Solange der Input nicht sagt,
jetzt ist gut, wird weiter dieser Zyklus hier durchlaufen,
und zwar ohne Ende.
Weil dieses Repeat eben keine obere Schranke hat für die Alternativen,
die dort erzeugt werden. Das sind beliebig viele. Und
auf die Art und Weise kann man eben die typischen Echtzeitanwendungen bauen,
die ja immer irgendwelche Daten in Empfang nehmen und darauf
sofort reagieren müssen mit einer Antwort
Jedes gute Echtzeitprogramm hat ja im Intern einen solchen Leseschreibzyklus drin,
der ständig auf dem Input horcht, ob dort Eingaben da sind,
um sofort die gewünschten Reaktionen auslösen zu können.
Iterationen, die wir jetzt behandelt haben,
egal ob wir das mit dem fehlergesteuerten Schleifen machen oder ob
wir das mit dem For All machen oder ob wir das mit dem Repeat-Aufruf machen,
Iteration oder all diesen Fällen von Iterationen ist gemeinsam,
dass ich keinen Bezug zwischen den verschiedenen Zyklen haben kann.
Der wird in der Imperativen Programmierung dadurch hergestellt,
dass ich eben den Zustand der Maschine verändere.
Das ist aber typischerweise nicht das, was man in der Logikprogrammierung machen will.
In der Logikprogrammierung möchte man ja vom Zustand der Maschine weitgehend unabhängig sein.
Und deshalb sollte man solche iterativen Konstrukte nur dann benutzen,
wenn es tatsächlich der Fall ist,
dass man diesen Bezug zu vorhergehenden Elementen nicht braucht.
Also, in dem Falle, dass das Berechnungsresultat irrelevant ist,
etwa überprüfen von Konsistenzbedingungen. Wenn ich also überprüfen will,
ob meine Datenbank stimmig ist, das weiß ich,
alle Altersangaben, die ich dort drin habe,
nicht größer als 200 sind
Dann kann ich einen for all über diese Datenbank
machen und kann diese Bedingung auf den einzelnen Elementen abtesten.
Und dann ist das Berechnungsresultat eigentlich völlig irrelevant,
sondern ich möchte nur wissen, ob die Bedingungen für alle Eintragungen erfüllt ist.
Das ist also eine typische Anwendung für so ein Iterationskonstrukt.
Und es wird immer dann sinnvoll einsetzbar sein, wenn ich Nebeneffekte habe.
Und nicht ohne Grund sind fast alle Beispiele,
die ich hier gebracht habe, Input-Output-Beispiele gewesen.
Also lesen Sie oder geben Sie irgendwie die Liste aller Eintragungen
aus der Datenbank über die Konsole aus.
Das ist ein typischer Nebeneffekt, ein Output zu erzeugen oder
auch etwas zu lesen.
Und dann sind solche Zyklen natürlich
sinnvoll einsetzbar
Sie sind aber nicht gut einsetzbar,
wenn ich etwa eine Liste auf Sortierung überprüfen will.
Da muss ich Klimmzüge machen, dass die zwischen den
einzelnen Iterationszyklen die Werte tatsächlich noch zur Verfügung stehen.
Andere Nebeneffekte können etwa die Manipulation der Datenbasis sein,
also trage eine ganze Reihe von Fakten in die Datenbank ein.
Das ist auch ein Nebeneffekt und dann kann ich das auch
gut mit so einem Iterationskonstrukt machen.
Oder aber, wenn ich globale Variablen habe und über
die globalen Variablen den Zustand der Maschine verändere
Also auch das ist ja ein Nebeneffekt, den ich erzeuge,
wo ich in einer globalen Bayablen oder aber auch eben in
der Datenbasis selber irgendwelche Daten abspeichere.
Und dann können solche Iterationskonstrukte wieder Sinn machen.
In dem Augenblick, wo ich im Kernbereich der Logikprogrammierung bin,
ist die Iteration eigentlich ein Störfaktor.
Etwas, was da nicht hinpasst und nicht hingehört
So großer Bereich, ist etwas,
was wir in den Grundzügen schon gesehen hatten und auch verwendet haben,
nämlich Resultatsaggregation, das Find All haben wir bereits behandelt.
Das müssen wir hier nicht nochmal machen. Also sammle alle Ergebnisse,
die beim Aufruf von Ziel entstehen, in Form von Thermen
in einer Liste zusammen. Und natürlich muss man dafür sorgen,
dass es irgendwie eine Kurvereferenz zwischen diesem Zielaufruf und der Struktur,
die hier steht, gibt,
damit über die Variablenbindung hier überhaupt etwas Sinnvolles hineinkommt.
Muss nicht immer sein, es gibt auch exotische Fälle,
wo es beispielsweise nur darum geht, für alle Dinge,
die hier positiv abgeleitet werden, irgendwie eine Eins in die Liste einzutragen.
Und zum Schluss mache ich eine Addition über die Liste
und weiß, wie viel positive Ergebnisse gibt es.
Dann ist also keine Chorreferenz hier dazwischen da.
Aber das sind exotische Fälle. In dem Normalfall wird hier
dazwischen, werden Co-Referenz-Beziehungen existieren müssen. Ja,
wir gucken uns im Weiteren so eine einfache Personaldatenbank an mit Personen,
Vorname, Nachname, Abteilungszugehörigkeit, Funktion und irgendeine fiktive Einkommensangabe.
Und das haben wir alle schon gesehen. Wir können jetzt
über dieser über dieser Mitarbeiterdatenbank jetzt uns bestimmte Teile heraussuchen und in
einer Struktur zusammenfassen und diese alle Instanzierungsvarianten dieser Struktur in der Ergebnisliste zusammensammeln.
Und dann kriegen wir eben so etwas heraus,
dass wir die fünf Personen, die wir dort haben,
mit ihrem Namen in dieser Strukturnahme, die hier also vorgegeben,
nach hier oben, hier vorgegeben ist, hier in der Ergebnisstruktur wiederfinden.
Das kennen Sie alles, das ist nichts Neues. Hier sind andere Varianten noch davon,
dass man eben hier nicht nur ein einfaches Ziel hat,
sondern ein komplexes Ziel,
das besteht also hier aus der Abfrage der Datenbank plus der Überprüfung dieser numerischen Beschränkungen.
Was haben wir hier? Ja, typischer Fall von Aggregationen,
die man aus der Welt der relationalen Datenbank kennt.
Suche nach einem Minimum oder einem Maximum hier ein Minimum.
Sorry, hier ist noch eine alte Variante des Minimums, Prädikats drin.
Das heißt jetzt Minimum, List ändert sich von Zeit zu Zeit.
Aber soll das Minimum aus einer Liste von Zahlen sein?
Also die Idee ist, ich sammle erst mal alle Gehälter,
die ich in der Datenbank drin habe,
in einer Liste Gehälter auf und suche dann das Minimum aus dieser Gehälterliste
und kriege also dann diese Gehälterliste einerseits und andererseits das Minimum,
was dort drin existiert.
Das ist offenbar gar nicht das Minimum, da hat irgendeiner mal drin rumgefummelt.
Sorry, es hätte das Minimum sein sollen. Ja,
so sieht man, wie Dateien über der Zeit degenerieren.
Schreiben Sie hier die 40.000 hin, dann sollte es das,
ist es das Minimum, ja.
Ja, solche Komplexen, Bedingungen können natürlich auch beliebige Prädikatsaufrufe sein.
Also hier die Abfrage, achso, hier ist es die Kombinationen.
Ah ja, das ist, das hier oben wäre die Aggregation über das Minimum.
Hier unten ist es die Aggregation über die Anzahl.
Also wie viele Elemente dieser Art gibt es? Das wäre
das Count im SQL etwa, dass man so umsetzen kann,
indem man erst die Liste aufbaut und anschließend zählt,
wie viele Elemente sind in der Liste drin.
Und das ist eben der Count-Klausel in der SQL-Abfrage gleichwertig.
Eine Erweiterung, die wir bisher noch nicht behandelt haben,
und die müssen wir uns mal ein bisschen genauer angucken,
ist das Prädikat Backoff.
Backoff hat ähnliche Funktionalität wie Find All,
aber es fast alle Alle Ergebnisse in Teillisten zusammen und
diese Teillisten kommen als Alternativen des Aufrufes von Backoff heraus.
Find All liefert ja immer eine Liste. Eine Liste für
alle Elemente, die die Bedingung erfüllen. Hier kriegen wir
Alternativelisten geliefert. Und zwar in Abhängigkeit davon von den Werten
der freien Variablen, die wir dort noch drin haben.
Und das müssen wir uns genau angucken. Was ist eine
freie Variable? Wir haben also hier die übliche Struktur Backoff
V Find All. Wir geben hier einen Thermen an.
Wir geben hier ein Ziel an und wir geben hier die
Liste an, in der wir die Ergebnisse gesammelt werden sollen
Und jetzt, wenn wir die Liste der Argumente vergleichen,
dann haben wir hier fünf Argumente, Vorname,
Name, Abteilung, die Funktion und das Gehalt.
Und hier haben wir nur vier davon. Die Abteilung ist
dir nicht genannt. Dann sind also die vajablen VNP und G,
die sind gebunden durch diesen Thermen. Oder mit anderen Worten,
ich möchte eine Bindung für diese Variablen haben als Ergebnis.
Was nicht gebunden wird, ist das A, das ist frei.
Das ist eine freie Variable in diesem Teilziel hier.
Und dann wird jetzt für alle Werte die Aha annimmt in der Datenbank,
wird jetzt gruppiert, welche Eintragung der Datenbank gibt es für die restlichen Variablen.
Also wir kriegen jetzt hier vier Ergebnisse und die sind sortiert nach A,
gleich Verwaltung, A, gleich Rechenzentrum, A,
gleich Montage und A, gleich Versand.
Das sind die vier Abteilungen, die in unserer Datenbank auftauchen.
Und wir kriegen dann zu jeder dieser,
diesem einzelnen Wert für die freie Variable A,
kriegen wir die entsprechende Liste, die genauso aussieht,
als wäre sie durch Fine All erzeugt.
Nur ist sie eben jetzt ein Back von vielen Backs,
ja, das ist, hier sind nur die drin,
die in der Abteilung Verwaltung angestellt sind.
Hier sind nur die drin, die im Rechenzentrum arbeiten und
so weiter und so
Auf die Art und Weise kann man jetzt also eine Gruppierung,
das ist eine typische Gruppierungsoperation,
die wir auch in SQL zur Verfügung haben,
wo wir nach die Ergebnisse gruppieren können, nach bestimmten vaiablen Belegungen.
Und das ist eben hier die Belegung der Variablen A.
So, das ist erstmal der Grundmechanismus. Also,
wir geben ein, wir haben einen Aufruf eines Prädikats,
wir haben ein Term, in dem Variablen enthalten sind,
die hier auch enthalten sind.
Und die werden dadurch durch diesen Term hier gebunden.
Und wir haben eine Differenz zwischen den Variablen,
die gebunden werden und den Variablen,
die in der Datenbank vorkommen
Und diese Differenz sind die freien Variablen, nach denen wird
das Ganze gruppiert. Nun, es ist hier so gewesen,
dass die Differenz nur genau eine Variable beträgt,
aber manchmal könnte die Differenz auch größer sein.
Das führt natürlich zu einer Kombinatorik. Wenn jetzt also,
was weiß ich, auch die Gehaltsangaben nicht gebunden sind,
dann würde auch nach den Gehaltsangaben sortiert werden. Und das können möglicherweise sehr,
sehr viele sein, weil es sehr wahrscheinlich ist,
dass es kommen zwei Mitarbeiter in einer Firma geben wird,
die das exakt das gleiche Gehalt haben.
Das heißt, dort kommt es dann dazu,
dass sich wegen der vajablen Gehalt,
die ich gar nicht haben will in der Struktur,
jetzt eine Zerlegung in So viele Einzelbuckets bekomme, wie ich Mitarbeiter habe.
Das ist natürlich nicht das Verhalten, was ich haben will,
sondern ich möchte sie nach anderen Kriterien ja auch noch gruppiert haben.
Und deshalb gibt es einen Mechanismus, wie man eine Pseudobindung einer Variablen herstellen kann.
Also hier möchte ich nur die Vornamen haben. Das heißt,
es wären, wenn ich nur die Vornamen haben will,
wären Name, Abteilung, Funktion und Gehalt, freie Variablen.
Und ich müsste alle Kombinationen aus Nachname, Abteilung und so weiter und Gehalt
Als Alternativen bekommen, das will ich nicht haben.
Ich möchte sie wieder nach der Verwaltung sortiert haben und nach nichts anderem.
Und dann kann ich eine Pseudobindung vornehmen und das wird mit diesem Dachoperator hier gemacht,
die sagt,
dieses N sei auch gebunden und das P sei auch gebunden und das G sei auch gebunden,
sodass wir im Endeffekt wieder vier Variablen haben, die gebunden sind.
Das V, das N,
das B und das G und übrig bleibt nur das A als
freie Variable und dann kriegen wir die Gruppierung wieder nach der freien Variablen angegeben.
Das ist also ein sehr, sehr mächtiger Mechanismus,
mit dem wir beliebige Gruppierungen über einer Datenbank generieren können,
die unter Umständen sehr,
sehr viele Attribute enthalten kann
Es gibt noch eine Variante davon, das ist das Set-Off,
das ist im Gegensatz zu dem Backoff.
Es verhält sich genauso wie das Backoff mit dem einzigen Unterschied,
dass die Ergebnislisten nochmal sortiert werden und Duplikaten frei sind.
Wenn ich also keine Duplikaten in den Ergebnislisten haben will,
dann ist das Set-Off die Methode der Wahl.
Ich gucke mal gerade, gibt es in unserer Datenbank überhaupt irgendetwas?
Das ist doch exakt das gleiche Ergebnis. Gut,
in der Datenbank gibt es keine Doppelergebnisse,
also sehen wir auch nach außen
keinen Effekt
Ja, hier ist eine Anwendung, über die gehe ich jetzt mal hinweg.
Das kann man sich angucken, wenn man möchte.
Früher lagen in der Bibliothek sogenannte Keyword in Kontextlisten aus,
wo der Titel eines Buches immer in verschiedenen Permutationsvarianten aufgelistet war.
Also nach den Stichwörtern sortiert, Einführung ist ein Stichwort,
mathematisch ist ein Stichwort und Logik,
ist ein Stichwort und dann wurde immer das so notiert,
dass man sagt, Einführung in die mathematische Logik,
aber auch mathematische Logik,
Einführung in die oder Logik,
Einführung in die mathematische Logik
Und dann diese Stichwortliste kann man sozusagen ausdrucken.
Und dann hat man die Buchtitel nach allen relevanten Stichwörtern,
die dort drin vorkommen, sortiert. Der einzig interessante Punkt,
den man sich nochmal angucken sollte, und dann überspringe ich das Ganze,
ist, dass hier eine Appent-Variante verwendet wird,
die eine Liste nimmt und in ihrer Bestandteile zerlegt,
in einen vorderen und einen hinteren Bestandteil.
Diese Liste ist der Titel,
den ich des Buches und wir sehen,
wir kriegen jetzt alle Zerlegungsvarianten und insbesondere sehen wir,
dass alle Wörter in der zweiten Liste
mal als Anfangselement auftauchen
Und das ist genau das, was ich haben will.
Also ich will es natürlich noch ein bisschen einschränken.
Es sollen in den Ergebnislisten alle Wörter mal hier vorn auftauchen
dürfen, so sie denn relevant sind.
Und das ist genau mit dem zweiten Teil dieses Aufrufes hier,
also mit der zweiten Argumentstelle gegeben.
Auf der zweiten Argumentstelle kriege ich also alle Restlisten,
die mit den Einzelwörtern beginnen. Und jetzt muss ich
nur dafür sorgen, dass sich die entsprechende Permutation bekomme.
Das ist also das, was ich hier von abgetrennt habe,
da hinten wieder hinzugefügt wird. Noch möglicherweise mit diesem Trend
Indikator. Und genau das wird hier gemacht,
das Rotieren der Titelliste, was die verschiedenen Varianten erzeugt,
ist einerseits die Zerlegung des des Titels in einen vorderen Teil
und einen hinteren Teil mit einem Trennwort dazwischen.
Und dann setze ich das Ganze eben wieder umgekehrt zusammen,
dass ich sage, ja, der hintere Teil,
der wird jetzt zum vorderen Teil.
Und der vordere Teil wird zum hinteren Teil und dazwischen mache
ich diesen Trennen Indikator. Und auf die Art und Weise
kriege ich all diese Rotationsvarianten für einen Titel heraus,
die kann ich jetzt noch beschränken, indem ich die Wörter,
die mich nicht interessieren, daraus filtere.
Das wird durch sogenannte Stoppwörter, also das ist das Ergebnis jetzt hier,
da sieht man eben all die Rotationsvarianten da.
Wir sehen jedes einzelne Wort, des ursprünglichen Titels,
der hier oben steht, taucht mal als erstes Wort in der rotierten Liste auf.
Und alles, was wir jetzt machen, ist,
dass die Varianten noch rausgestrichen werden, die wir nicht haben,
wollen wir definieren sogenannte Stoppwörter.
Also Artikel und Präposition beispielsweise. Und fordern hier noch,
dass das Wort, was hier abgetrennt worden ist,
keines dieser Stoppwörter sein darf.
Wenn es ein Stoppwort ist, dann scheitert das Ganze
hier und dann geht es mit der nächsten Zerlegungsvariante weiter.
Dann kommt er ja nie hier hin.
Und dann sehen wir das tatsächlich nur noch,
die Rotationsvarianten übrig bleiben, die wir haben wollen.
Also Einführung in die mathematische Logik ist dann
eben mit diesen drei relevanten Stichwörtern
am Anfang vertreten
Gut, und das können wir jetzt einbauen in eine gesamte Stichwortliste,
wo wir eben jetzt das Set-Off verwenden und auf die Art und Weise auch gleich das Sortieren mit erledigt bekommen.
Set-Off sortiert, ja. Und wo wir hier diesen Bindungsmechanismen verwenden,
dass wir also nach den Titeln, die dort ermittelt werden,
eigentlich zerlegen würden, denn es wäre eine freie Variable.
Wir machen also für alle Elemente der alten Liste,
wo die Titel alle drinstehen,
Wenn wir dieses Rotieren an und wir wollen diese,
was immer das Rotieren liefert, in die neue Liste aufnehmen,
dann wäre der Titel hier eine freie Variable und wir verhindern,
dass, indem wir ihn hier dadurch noch binden.
Das ist sozusagen ein Missbrauch des Set-Offs, von der ursprünglichen Funktionalität des Set-Offs,
haben wir hier fast nichts verwendet, außer dem Sortieren.
Und die Zerlegung in einzelne Teillisten,
etwa haben wir durch diese Pseudobindung
hier außer Kraft gesetzt
Ja, das ist eigentlich,
und damit bleibt nicht viel mehr übrig,
ich kriege eine große Liste heraus und das Set oft garantiert mir,
dass sie erstens sortiert ist und zweitens duplikatenfrei ist,
dass die Dupelkartenfreiheit sollte, doch, die spielt auch eine Rolle,
doppelte Eintragung verschwinden dann aus der Liste.
Das ist auch ein Effekt, der dadurch passiert. Gut,
das ist noch meine Anwendungsbeispiel dafür,
wie ich also jetzt mit einem solchen Prädikat höherer Ordnung
hier mit sehr wenig Programmieraufwand relativ komplexe Ergebnisse erzeugen kann.
Und das ist dann ein Beispiel dafür,
ich gebe eben eine solche Liste von da oben rein und ich kriege
diese unterschiedlichen Varianten sortiert nach den relevanten Stichwörtern hinten wieder heraus.
Konsistenzforderung, auch ein Prädikat höherer Ordnung, haben wir bereits gesehen in der Verwendung als Zyklenkonstrukt.
Wir können es eben auch einfach verwenden,
um Konsistenzbedingungen in der Datenbank zu überprüfen,
indem wir eben jetzt einfach Hier ein,
das ist die Definition des For Alls,
wir machen einen Find All über,
die ist mit dem gegebenen Teilziel hier und sammeln die Ergebnisse auf und dann müssen wir die Ergebnisse nochmal überprüfen,
ob sie irgendeine bestimmte Bedingung erfüllen.
Und das ist hier im Prinzip aufgetragen. Also eine Möglichkeit,
eine Datenbank als auf eine bestimmte Bedingung zu überprüfen.
Auch ein Beispiel, wo Iterationen angebracht ist,
weil ich eben keine Beziehung zwischen unterschiedlichen Einträgungen
überprüfen muss
Es gibt eine effizientere Implementierung als diese,
also das Ineffiziente an dieser Applivertierung von For All, ist,
dass ich erst eine Liste aufbaue und anschließend die Liste Rico sich wieder abarbeite und gucke,
ob die Ergebnisse tatsächlich die sind, die ich haben möchte.
Hier ist eine effizientere Implementierung, die also sofort,
wenn das erste Element gefunden wurde,
was nicht die Bedingung erfüllt, scheitert.
Also ich rufe das Ganze wieder mit einem Ziel und einer
Bedingung auf. Ich aktiviere das Ziel und wenn die Bedingung
nicht erfüllt ist, dann lasse ich das Ganze scheitern
Und falls es denn hier oben irgendwann mal... Ach,
der Cut sorgt natürlich dafür,
dass diese zweite Klausel nicht mehr sichtbar ist.
Wenn denn das Ziel über alle Alternativen erfolgreich abgearbeitet worden ist,
ja, hier gibt es ja Entscheidungspunkte,
die durch das Fehl im Backtracking wieder aktiviert werden.
Wenn denn diese Ziele keine Entscheidungspunkte mehr liefern, dann sorgt diese Catch-All-Klausel dafür,
dass zum Schluss wenigstens ein Gesamtergebnis Truh übergeben wird.
Denn das möchte ich ja,
wenn die Bedingungen für alle Elemente gilt,
dann möchte ich ja zum Schluss Truh
rausbekommen und nicht Forz
Gut, also kann ich dann jetzt hier eben testen.
Gilt, dass alle Mitarbeiter mehr als 10.000 Euro verdienen.
Ja, gilt, dass alle mehr als 50.000 Euro verdienen.
Nein, wir hatten ja vorhin das Beispiel mit den 40.000 Euro.
So, und damit sind wir bei dem entscheidenden Punkt angekommen.
Das ist nämlich der Übergang zur Metaprogrammierung. Metaprogrammierung,
als das ein Trick, wo ich durch ein Logikprogramm,
andere Logikprogramme verarbeiten lasse.
Das Logikprogramm, also das Space-Programm ist dann sozusagen Date,
sind Daten für das Metaprogramm
Jetzt ein Programm, was andere Programme manipuliert.
Anwendung davon gibt es jetzt sehr viele.
Wir werden also beispielsweise sowas nennen, das machen wir nicht.
Ein Pretty-Print da. Ein Pretty-Print da nimmt eine Prädikatsdefinition
her und druckt sie nach bestimmten Styleguides aus,
also mit Einrückungen und irgendwelchen farblichen Markierungen einzelner Elemente.
Das ist ein typischer Anwendung von Meta-Programmierung. Ich nehme ein Programm,
ich manipuliere es wenigstens nur in seinem Aussehen,
nicht in seiner Samantik und gebe das Ergebnis wieder für den Nutzer raus,
sodass er das leichter verstehen kann, wo die relevanten Zusammenhänge sind.
Ich kann Programme transformieren, indem ich etwa die Programme in neue Programme übersetze.
Das wird also standardmäßig zur Programmoptimierung benutzt. Im Falle der Tail-Recursion,
also wann immer ich ein end-rekusives Programm habe,
wird das intern sofort in einem iterative Implementierung umgewandelt und ist dadurch effizienter,
weil es eben den Stack nicht benutzt.
Ich kann so etwas auch machen, dass ich erkenne,
dass bestimmte Teile des Programms für die aktuelle Abarbeitung überhaupt keine Konsequenzen haben.
Dann kann ich den Aufruf dieser Teile des Programms verzögern und
später erst aktivieren
In der Hoffnung, dass möglicherweise in der Zwischenzeit
andere Teile des Programms die Gesamtabarbeitung überflüssig machen.
Und das kann ich durch eine formale Analyse des Programmtextes herausbekommen,
kann Teile des Programms von der Abarbeitung schützen.
Bis zu dem Zeitpunkt, wo das Ergebnis wirklich gebraucht wird.
Und dadurch möglicherweise Optimierungseffekte erzielen. Kompilierung ist natürlich auch etwas.
Ich nehme ein Programm und übersetze es in ein neues Programm.
Möglicherweise natürlich kombiniert mit Optimierungsschritten, die ich dabei vornehme
Wir werden auch noch im Zusammenhang mit den Differenzlisten einen speziellen,
eine spezielle Notationsform für Grammatiken kennenlernen, die auch damit arbeiten,
dass sofort beim Einlesen des Programmes das Programm transformiert wird in eine interne Form.
Und diese interne Form wird dann abgearbeitet.
Nicht das, was ich nach außen hin aufgeschrieben habe.
Wofür wir hier uns interessieren, ist der dritte Punkt,
das sind nämlich die Meta-Interpreter,
die eine bestimmte Form der Programmabarbeitung simulieren sollen
Also ein Meta-Interpreter ist immer etwas,
was für eine Programmiersprache geschrieben ist und also was Ausdrücke einer
Programmiersprache verarbeitet und in der Programmiersprache selbst geschrieben worden ist.
Das ist der einfache Fall, mit dem wir einsteigen.
Später werden wir dann dazu kommen, dass wir
eben in Prolog ein Interpreter für eine andere Programmiersprache schreiben.
Aber hier Meta-Interprete heißt immer in der Programmiersprache selbst.
Den einfachsten Meta-Interpreter haben wir schon kennengelernt, das war das Cole.
Das ist aber in dem Sinne noch kein wirklicher Meta-Interpret.
Es ist einer doch, ja. Aber er tut
nichts richtig
Richtig Sinnvolles, weil er nämlich das Programm einfach so oder
das Teilziel so aufruft, wie es hier gegeben ist.
Also er bringt keine eigene Funktionalität ein in die Abarbeitung.
Das ist einfach nur, ich übergebe es
dem Prolog-System und das wäre sonst auch passiert.
Also da ist nichts Spannendes dazwischen. Also wir brauchen,
wenn wir so einen richtigen Meta-Interpreter schreiben wollen,
wo wir jetzt die Funktionalität tatsächlich verändern,
dann brauchen wir erstmal ein Grundinterpreter,
der die Arbeitsweise des Prolog-Systems beschreibt
Und wir brauchen also, irgendwie müssen wir das Berechnungsmodell,
was Prolog zugrunde liegt, also mit all seinen Details,
Suche, Unifikation und so weiter,
das müssen wir jetzt selber schreiben.
Denn das wollen wir nicht mehr dem Prologsystem überlassen.
Und dafür haben wir eine Grundstruktur, die in allen Anwendungen immer wieder auftaucht.
Und die gucken wir uns gleich an. Wir schränken das Ganze
ein bisschen ein, allerdings nicht in seiner vollständigen Funktionalität.
Das Einzige, was wir fordern, ist,
dass der Körper eine Konjunktion von positiven Literalen ist.
Also was wir ausschließen, ist der Cut und die Negation.
Ansonsten ist praktisch alles erlaubt hier
Gut, und dann sieht ein Prolog-Interpreter so aus,
der macht jetzt auch nichts anderes,
als dass er Prolog in Prolog abarbeitet.
Es ist ein Prolog-Programm, was ein anderes Prolog-Programm nimmt und abarbeitet.
Gut, was tut es? Also wenn das Prolog-Programm nur
aus der Klausel Tru besteht, dann ist er fertig.
Dann ist das Ganze positiv abgearbeitet und er muss sich nicht mehr um die Alternativen kümmern.
Deshalb hier dieser Cut drin. Also es ist wirklich
nur die konstante True. Und die sagt mir,
ja, die Abarbeitung ist vollständig korrekt bearbeitet worden.
Andererfalls könnte das Teilziel, was abzuarbeiten ist,
aus der Konjunktion, nein, das Ziel,
was abzuarbeiten ist,
könnte aus der Konjunktion zweier Teilziele bestehen
Die durch Komma voneinander getrennt sind. Da ist zum Beispiel nichts darüber gesagt,
ob dieses Ziel hier nicht möglicherweise wieder ein komplexes ist.
Es sagt nur, es gibt ein einfaches Ziel und
es ist gefolgt von weiteren Zielen, einem oder mehreren.
Wenn ein Ziel diese konjunktive Struktur hat, dann setze wieder einen Cut,
rufe den Metainterpreter mit dem ersten Teilziel auf,
rufe den Metainterpreter mit dem zweiten Teilziel auf und du bist fertig.
Und wenn ich denn ein Ziel habe, was weder Tru ist,
noch eine Konjunktion von Teilzielen, elementares Ziel,
dann suche bitte aus der Datenbank eine Klausel mit diesem Ziel und gib mir ihren Körper und nach Instanziierung zurück.
Das ist einer der Basisprädikate, die das Prologsystem zur Verfügung stellt.
Das hatten wir bei den extra logischen Prädikaten uns bereits angeguckt.
In der Metalogisch ist das, das ist metalogisch.
Also das nimmt das Ziel und gibt mir über das Backtracking
alle Klauseln, wo der Kopf mit diesem Ziel unifiziert
Und zwar gibt es mir nicht die Klauseln,
sondern sofort den Körper der Klausel, wobei alle Variablen,
die hier bei der Unifikation instanziert worden sind,
auch hier bereits instanziert sind.
Dass dieser Aufruf von Klaus ist,
also ein ganz zentrales Element, der Abarbeitung von Logikprogrammen,
nämlich das ist der Teil, wo die Suche organisiert wird.
Hier werden jetzt Alternativen bereitgestellt in Form von vorinstanzierten Klauselkörpern,
die über das Backtracking abgearbeitet werden müssen.
So, und nun brauche ich nichts anderes zu machen,
als ich nehme diesen Körper dankbar in Empfang und schicke ihn
wieder in den Metainterpreter hinein
Und damit habe ich einen Interpreter, der nach außen
immer noch so aussieht, als würde er Prolog abarbeiten.
Aber in Wirklichkeit ist das System nur noch für Teile dafür verantwortlich.
Nämlich genau für diesen Teil hier. Gib mir aus der
Datenbank die passenden Klauseln. Das macht noch das Prolog-System.
Der gesamte Rest der Abarbeitung, der ist bereits in unserer Hand. So,
und wenn wir das jetzt also aufrufen mit My Last von A,
dann kriegen wir eben, wie sich das gehört,
die einer Liste, die AS-Element hat, die Zweierliste,
die A als letztes Element hat,
die Dreierliste,
die A als letztes Element hat und so weiter
Wir geben ihm hier also jetzt ein Programm, ein Prolog-Programm,
in dem wir eben dem Aufruf sagen, wie müsste es aufrufen.
Und er arbeitet dieses Prolog-Programm ab. Und zwar so,
wie wir das gerne erwarten würden. Nach außen hin hat sich
also an der Funktionalität nichts verändert. Intern ist jetzt aber
genau dieses Meta-Programm, dasjenige, was die Verarbeitung macht.
Das geht also durch und sagt, okay,
ich soll hier ein Ziel abarbeiten, ist es das Ziel, Truh?
Nein, ist es nicht. Ist es eine Konjunktion von
Teilzielen? Ist es auch nicht. Also geh in die
dritte Klausel. Gib mir bitte alle Klauseln,
wo dieser Aufruf auf den Kopf unifiziert
Und das wird im ersten Falle eben der Rekussionsabschluss sein,
den ich für My Last habe. Und mit diesem Rekussionsabschluss
gehe jetzt in die Verarbeitung wieder hinein. Da muss man dazu wissen,
ich bin mir nicht sicher, ob ich das jemals diskutiert habe,
also dass My Last hat ja als Rekussionsabschluss,
wie rum ist es, Element, wenn das Element,
das einzige Element der einer Liste ist,
dann ist es das letzte Element der einer Liste.
Das ist ein Fakt, ein typischer Fakt.
Im Prolog-System selber ist aber alles in Form von Klauseln abgespeichert.
Das heißt, tatsächlich steht im Prolog-System Das ist drin.
Und deshalb funktioniert das Ganze jetzt hier. Er kriegt also
diesen Fakt zu greifen, kriegt einen Körper, der heißt True.
Bei Jablien gibt es in dem nicht.
Also muss auch nichts instanziert werden. Und diesen Körper schickt
er wieder in die rekursive Verarbeitung hinein.
Und dann fällt er natürlich hier oben sofort rein und sagt,
ah, ja, ich habe ein True gefunden,
also schneide alle Alternativen Klauseln weg und ich bin fertig.
Dadurch kommt das erste Ergebnis zustande. Wenn ich jetzt
das Backtracking aufrufe, dann wird eben hier die nächste Klausel
aus der Datenbank gesucht
Nächste Klausel ist eben genau der Rekursionsschritt, schmeißt er das
erste Element weg und nimmt das Ganze mit der Restliste.
Das ist in diesem Falle auch wieder ein einfaches Ziel im Körper
der Klausel, steht keine Konjunktion drin. Das heißt,
in diesem Fall komme ich wieder hier in die dritte Klausel hinein.
Es wird mit dem Zeilziel, also dem Körper des vorhergehenden Aufrufes,
wird wieder die Suche in der Datenbank gestartet.
Die erste Klausel, die passt, wird genommen.
Das ist wieder unser Rekursionsabschluss. Und dieser Rekursionsabschluss liefert uns
wieder dieses Truh hier und damit kriege ich das zweite Ergebnis
über die erste Klausel hier oben
Also auf die Art und Weise sehen wir die tatsächliche Abarbeitung,
das sind nur diese drei Fälle, die wir unterscheiden müssen.
Entweder ich habe einen Fakt und das Ziel, was ich ableiten muss,
ist True, oder ich habe eine Konjunktion von Peilzielen,
da muss ich sie alle beide abarbeiten.
Oder ich habe ein einfaches Teilziel,
da muss ich mir eben Fakten aus der, na Quatsch,
Klauseln aus der Datenbank suchen und mit diesen gefundenen Klauseln neue Ziele ableiten,
indem ich den Kopf durch den Körper ersetze und das,
was ich dabei an Alternativen ergibt,
wieder in die Suche hineinstecken
Das ist alles, was passiert. Nach außen hin haben wir
noch nicht viel erreicht, aber wir haben die wesentliche Voraussetzung geschaffen,
dass wir jetzt anfangen können, die Semantik zu manipulieren.
Denn jetzt haben wir noch nicht alles, aber es ist
schon sehr, sehr, sehr viel in der Hand.
Das habe ich versucht, mit dieser Grafik zu verdeutlichen.
Wir haben hier unten einen Basislevel, das ist also unsere,
unser Prädikat, was wir definiert haben,
die klassische Definition für das letzte Element einer Liste.
Und wir setzen jetzt diesen Meta-Interpret da oben drüber,
der jetzt immer guckt, wo sind die entsprechenden Elemente auf dem Basislevel.
Also beispielsweise hier dieses eine Klausel sucht und wo der Kopf
mit dem Ziel, was ich hier gegeben habe,
Also finde mir eine Klausel, wo ich den Kopf mit dem gegebenen Ziel unifizieren kann.
Oder eben hier hinten liefer mir einen Körper zurück,
der nach vajablen Substitutionen zu diesem hier gefundenen Klauselkopf passt.
Also ich habe ein Ziel, ich gehe hier rein,
ich finde die Klausel, ich instanziere dadurch die vajablen,
ich kriege den Körper der Klausel und der kommt zurück nach
dir oben und wird dann hier vom Metainterpreter wieder abgearbeitet.
Das ist die Idee von Metaprogrammierung. Es schwebt also ein Programm oben drüber,
was immer unten auf die Basislevel-Definitionen schub schaut und dort
die relevanten Elemente identifiziert und dann nach entsprechender Verarbeitung hier oben
der Verarbeitung wieder zur Verfügung stellt
So, und wofür können wir das jetzt benutzen?
Warum machen wir den ganzen Trick? Also,
wir können, und das machen wir jetzt tatsächlich auch,
uns eigene Trace und die Backwerkzeuge bauen.
Wir können aber auch uns, also ein Trace und die Backwerkzeug,
sollte ja an der Semantik des Programms noch nichts direkt verändern.
Die Verarbeitung soll ja die gleichen Ergebnisse liefern wie im Normalfall,
sonst nutzen es der beste Treser nichts.
Wir können aber auch hingehen und die Semantik des Interpreters verändern.
Entweder auf der Ebene der operationalen Semantik oder aber auch auf
der Ebene der innotationellen Semantik
Dann müssen wir die Inferenzregeln modifizieren, die im Prolog-System realisiert wird.
Und das ist eben die operationale Semantik. Standardinferenzregel heißt,
leite das Ziel aus den Klauseln in der Datenbank ab,
mit Verwendung einer bestimmten Inferenzstrategie. Und die Inferenzstrategie heißt Top-Down,
also fange vom Ziel an und versuche die Theorie mal abzuleiten.
Tiefe zuerst, nimm erst ein Ergebnis und verfolge
erst bis die Suche terminiert und links nach rechts,
wenn du mehrere Teilziele im Körper einer Klausel hast,
arbeite sie von links nach rechts ab.
Diese drei Festlegungen legen und das Es wird auch als SLD-Resezolution bezeichnet,
die drei Festlegungen bestimmen, wie unser Prologprogramm vorgeht.
Und das können wir natürlich auch modifizieren. Daran sind wir
nicht zwangsweise gebunden, wenn wir wollen, können wir das ändern.
Also wir können beispielsweise die operationale Semantik ändern,
indem wir nicht eine Top-Down-Suche machen, sondern eine Wappen-Absuche.
Das ist etwa in einem Teil von Polog, also in Data-Look,
das ist nämlich genau der Bereich unserer deduktiven Datenbanken.
Sie erinnern sich, zweites Kapitel, die deduktiven Datenbanken werden
üblicherweise Buttom-Up abgeleitet und nicht Top-Down wie ein Prolog-Programm.
Dafür gibt es gute Gründe, dass man das so macht
Jetzt haben wir die Möglichkeit in der Hand,
unter Nutzung des gesamten Services, des Prolog-Systems,
eine alternative Suchstrategie zu implementieren. Im Bereich des Parsings,
wo ich also die Strukturanalyse für komplexe Strukturen mache,
also arithmetische Ausdrücke, natürlich sprachliche Ausdrücke und so weiter,
Gensequenzen, dann ist es oftmals auch so,
dass Buttom-Up-Suche Vorteile hat, gegenüber der Top-Down-Suche bezüglich der Robustheit etwa.
Also Top-Down-Suche ist immer sehr, sehr empfindlich gegen Das Scheitern von Teilzielen
aufgrund von kleinen Abweichungen, an die man nicht gedacht hat.
Buttom-Absuche erlaubt da üblicherweise robustere Verfahren. Ist aber natürlich auch
teurer. Breite-zuerst-Verarbeitung, ja. Ich erinnere an das Beispiel
mit dem endlichen Automaten. Wir kriegen andere Ergebnisse,
wenn wir Breitensuche mache im Vergleich zur tiefen Suche,
für bestimmte Anwendungen natürlich.
Oder aber auch parallele Bearbeitung von Teilzielen. Warum muss sich
alle Teilziele immer sequenziell verarbeiten? Kann ich die nicht parallel
in Freds hineinschicken und gucken, was dabei herauskommt und hinterher
die Ergebnisse integrieren
Ich kann aber auch die Semantik des Systems einschränken,
indem ich zum Beispiel sage, ich möchte gar nicht
alle Ergebnisse haben, die mir das System liefert.
Und das ist insbesondere dann, wenn das System in einen endlosen Zyklus läuft,
das will ich nicht haben, das will ich einschränken.
Also ich kann einen Interpreter schreiben, der nur Teile der gewünschten Ergebnismenge produziert.
Oder aber ich kann auch die Ergebnismenge erweitern, indem ich sage,
naja, ich möchte beispielsweise so etwas wie Vererbung einführen.
Wenn ich also eine ISA-Beziehung habe,
eine Kategorie A ist Teil von einer Kategorie B und ich kann
das Ganze für die Kann das Ganze dann auf der Ebene
der generelleren Kategorien beschreiben und über die Vererbungsrelation weiß ich,
dass es auch für die Untergeordneten dann gelten muss.
Also an der Stelle gehe ich dann hart in die demotationelle Semantik hinein.
Dann erweitere ich die Klauselmenge, die ich aus der Datenbank
ableiten kann. Gut, das war der falsche Knopf.
Wir fangen also an mit dem einfachsten Ding.
Das ist ein Trace-Werkzeug. Ich habe jetzt Visible Prolou genannt
. Wir wollen jetzt die Programmabarbeitung protokollieren
Wie immer mit einem einfachen System an und erweitern es dann um Funktionalität.
Und das kann man nach Belieben dann fortsetzen, wenn man
das möchte. Also, was wir erstmal haben wollen,
ist nicht mehr als, welche Teilziele werden der Reihe nach
aufgerufen und wie ist die rekrusive Einbettung dieser Aufrufe?
Mehr soll das System erstmal nicht machen. So, das
ist die Lösung dafür. Wir sehen wieder, wir haben
die Grundstruktur unseres Interpreters. Wir haben hier, was,
der heißt jetzt eben nicht Pip wie vorhin für Prolook in Prolook,
sondern ein Visual Interpreter, visual prologe.
Wenn denn dieses Ziel, was wir hier haben,
tue ist, dann bist du fertig.
Wenn denn das Ziel eine Konjunktion von Teilzielen ist,
dann rufe erst das eine Teilziel auf, dann das andere Teilziel.
Und wenn denn es ein elementares Teilziel ist,
dann suche eine relevante Klausel aus der Datenbank,
tue irgendetwas und rufe das Ganze rekursiv wieder auf.
Diese Grundstruktur haben wir hier auch wieder.
Was neu hinzukommt, ist eine weitere Argumentstelle. Und auf
dieser weiteren Argumentstelle wird die Einrücktiefe für die Protokollierung verwaltet.
Initial, und das ist dieser Rapper hier außenrum rum,
dieser Rapper setzt initial diese Einrücktiefe auf die leere Zeichenfolge.
Also ganz oben bin ich am linken Rand. Und irgendwo zwischendrin,
hier an dieser Stelle, wird die alte Einrücktiefe um einen,
hier in diesem Falle zwei Leerzeichen erweitert und ich kriege eine neue Einrücktiefe.
Und diese neue Einrücktiefe schicke ich wieder in die Rekussion hinein,
damit sie im nächsten Schritt berücksichtigt wird.
Gut, das ist die eine Erweiterung auf dem ersten Argument,
also die Einrücktiefe verwalten.
Das war Schritt Nummer eins und Schritt Nummer zwei ist,
wir wollen sichtbar machen, welche Teilziele aktiviert worden sind.
Und das machen wir ganz einfach hier.
Wir schreiben natürlich die aktuelle Einrücktiefe auf die Konsole
Dann schreiben wir das Fragezeichen minus,
also sozusagen den Prompt des Systems,
geben wir da noch an und anschließend schreiben wir das Teilziel,
was wir hier zur Suche nach dem relevanten Klauseln benutzt haben,
das schreiben wir ebenfalls auf die Konsole und machen einen Zeilenvorschub.
Das ist eigentlich schon alles, was passiert ist.
Also zwei Elemente, einerseits hier protokolliere den aktuellen,
den aktuellen Aufruf mit der aktuellen Einrücktiefe,
hier erhöhe die Einrücktiefe um einen bestimmten Betrag und initialisiere
die Einrücktiefe hier oben mit der leeren Zeichenkette
Wet's all. Mehr brauchen wir nicht zu machen.
Und jetzt können wir uns angucken, was passiert.
Wir rufen jetzt also wieder my last auf,
welche Elemente enthält die Liste A, B, C.
Unter der Regie dieses Visual Prolog-Interpreters,
der also jetzt schreiben soll, was passiert alles.
Und was passiert ist, ich kriege als erstes einen Aufruf von,
mit der gesamten Liste und einer bestimmten, einer uninstanzierten Variablen hier.
Das ist also jetzt nur der Meper-Interpreter, reicht dieses Teilziel
jetzt durch an die Verarbeitung. Das ist der erste Schritt,
dass also der Metainterpreter sagt, ja, ich soll dieses Teilziel abarbeiten.
Im zweiten Schritt sehen wir rekursiv abgearbeitet, das erste Element ist entfallen,
das Ganze passiert nur noch mit der Restliste.
Und im dritten Schritt dann wird,
passiert das nur noch mit der einer Liste und dann sind wir wieder bei dieser,
bei dem Rekursionsabschluss angekommen und jetzt wird hier diese Variable mit
C instanziert und wir kriegen das als Ergebnis ausgegeben.
Hier meldet sich jetzt wieder das Prolog-System. Das haben wir
nicht programmiert. Das ist das Basissystem, Prolog meldet sich wieder.
Das hier, das macht unser Meta-Interpreter. Das ist das Prolog-System. Und wenn wir jetzt nach Alternativen fragen,
Ja, schade, das habe ich hier nicht mit dabei,
dass Semikola nicht mit angegeben, wenn wir nach Alternativen fragen,
dann wird eben noch die zweite Klausel,
die wir haben, nämlich der Rekursionsschritt verwendet,
aber der führt zu keinem weiteren Ergebnis und dann meldet sich das Prologsystem wieder und sagt,
ja, weitere Ergebnisse gibt es nicht,
also False.
So, auf die Art und Weise können wir uns also
erstmal veranschaulichen, was passiert. Natürlich ist das als Dress-Werkzeug
noch nicht so richtig geeignet. Wir sehen hier nicht wirklich,
was der Aufruf ist und was die Klausel ist,
die dort angefasst wird.
Also das Ganze hätten wir noch
ein bisschen genauer
Und das können wir natürlich jetzt nach belieben genauer machen,
was hier fehlt, offensichtlich ist, dass ich eben immer genau unterscheide,
einerseits, was war das Ziel,
das ich erfüllen will und was ist die Klausel,
die ich jetzt zur Erfüllung des Ziels anfasse.
Ach so, sorry, hier habe ich noch eine andere Variante
des Aufrufes, sage, die bringt nichts Neues dazu.
Hier ist das Ganze jetzt eben, generieren wir alle Listen,
die er als letztes Element haben und das wird eben hier mit dem Rekursionsabschluss aufgerufen.
Damit kriege ich ein Ergebnis, wenn ich nach Alternativen frage,
wird der Rekursionsschritt benutzt, der Rekursionsschritt führt dazu,
das rekursiv das Ganze wieder aufgerufen wird und mit dem Rekursionsabschnitt Abschluss kriege ich das nächste Ergebnis.
Wenn ich nach Alternativen frage, wird wieder der Regulsionsschritt bemüht,
der ruft den Regulsionsabschluss auf und ich kriege das dritte Ergebnis
und so weiter und so fort.
Solange wie ihm meine Geduld reicht. Und was wir hier sehen,
ist eben immer sehr schön,
dass wir hier bei den Zwischenergebnissen hier uninstanziierte Elemente drin haben,
die aber dann in allen Ergebnissen immer wieder dieselben sind.
Und hier an der zweiten Stelle kommt immer das 274er-Dings
und so weiter. Wenn ich weitere Ergebnisse hätte,
wäre eben das dritte
immer dieses 293er-Viable
Gut, das ist nur noch mal ein zweiter Aufruf dafür gewesen.
Die Erweiterung ist jetzt also, ich möchte all die zum Vergleich
herangezogenen Klauseln sehen. Und ich möchte auch das Unifikationsresultat sehen.
Also war die Unifikation mit dem Klauselkopf erfolgreich oder nicht?
Und damit kann ich also auch die erfolglosen Klauselaufrufe protokollieren.
Das ist ein Service, den auch der eingebaute Dresser nicht macht.
Der bringt uns ja immer nur die erfolgreichen Aufrufe für der Suche.
Nicht die Erfolglosen. Wir wollen es jetzt auch die Erfolglosen
sehen. Und wenn wir das haben wollen,
müssen wir zwei Sachen voneinander trennen,
die zurzeit noch in einer und demselben Ding drin sind und
nämlich genau an dieser Stelle unseres Major-Interpreters, wo dieser Chlors-Aufruf erfolgt.
Dieser Chlors-Aufruf liefert uns den Körper der relevanten Klausel,
aber er unifiziert auch gleichzeitig den Kopf mit dem Ziel.
Wenn wir also wissen wollen,
welche Klausel ist angewendet worden und auch wissen wollen,
möglicherweise die Klauselverwendung scheitert, dann müssen wir das hier aufspalten.
Das ist der Punkt, wo noch zu viel Funktionalität
vom System in einem einzelnen Aufruf drinsteckt.
Das müssen wir aufspalten in, suche mir die relevante Klausel,
aber unifiziere noch nicht
Und erst nachdem wir protokolliert haben, dann unifiziere.
Das müssen wir auseinandernehmen und das passiert dir. Das ist
das Programm und wir sehen, es wird nicht schrecklich komplizierter.
Also sorry, es ist die dritte Klausel.
Nur noch die, die jetzt ein elementares Ziel verarbeitet.
Die anderen beiden für den Truhfall und für die Konjunktion,
die sind dir nicht mit aufgeführt.
Wir müssen also als erstes ein uninstanziertes Teilziel erzeugen.
Nämlich, das ist das Pattern, was ich brauche,
um nach relevanten Klauseln zu suchen, ohne dass bereits unifiziert wird.
Dazu brauche ich ein Ziel, was den gleichen Funktor hat,
die gleiche Stelligkeit hat, als Argumente nur uninstanzierte Variablen enthält.
Es gibt verschiedene Möglichkeiten, das zu erreichen.
Hier sind zwei aufgeführt, damit man auch mal sieht,
wie man die ganzen Dinge verwendet, die wir inzwischen eingeführt haben.
Also wir nehmen das Ziel, G1 und zerlegen es in
eine Liste aus dem Funktor und seinen Argumenten.
Je nachdem, wie viele Argumente es gibt,
ist die Miliste länger oder kürzer. Dann sagen wir,
wie lang sind denn bitte schön, wie lang ist diese Restliste hier?
Also mit anderen Worten, wie viele Argumente gibt es?
Und dann rufen wir dieses Lenk wieder umgekehrt auf und sagen,
gib mir bitte eine neue Liste der gleichen Länge
Und diese neue Liste wird lauter uninstallsierte Variablen enthalten.
Sie ist aber genauso lang wie diese hier.
Und dann bauen wir uns aus dem alten Prädikatsnamen,
dem Funktor und der neuen Liste uninstanzierte Argumente eine neue Liste
zusammen und wandeln die wieder in eine Struktur um.
Also was hier passiert ist, wir haben so etwas
wie A von 3, 4, 5.
Dieses, das ist unser G1. Wir zerlegen das in eine
Liste aus Prädikat, aus Funktor und Argumenten.
Das ist also dann eine Liste A3,
4,
Wir ermitteln die Länge dieser Restliste hier hinten, das ist drei.
Wir erzeugen eine neue Liste mit drei Elementen.
Ich schreibe jetzt einfach mal anonyme Variablen hier hin,
das sind natürlich nicht anonyme Variablen, sondern die Gensüm-Variablen,
die vom System erzeugt werden.
Und wir bauen vor diese Liste den alten Funktor davor.
Ich kürze mal ein bisschen ab,
indem ich den hier einfach davor schreibe
Und diese Liste wandeln wir jetzt wieder um in einen Term,
in dem, dass der Funk da wird und dass die Argumente,
also ist die neue, die neue Struktur,
die wir haben, sieht so aus.
Das ist eine Möglichkeit aus einer instanziierten oder teilinstanziierten Struktur,
eine vollständig uninstanziierte zu machen. Und die können wir jetzt benutzen,
um alle Klauseln zu finden, die ein dreistelliges A verwenden.
Und genau das wollen wir. Die wollen wir protokollieren. Die Alternative,
wie man das auch machen kann, ist hier hinten angegeben.
Wir hatten ein Prädikatfunkter, was einen Term nimmt mir hier
an der zweiten Argumentstelle den Funktor liefert und an der dritten Argumentstelle die Anzahl der Attribute.
Und auch das Ding kann man in allen Richtungen verwenden.
Ich kann jetzt also das Ding nehmen und mir hier einen Aufruf erzeugen,
der die gleichen Funktor hat wie hier oben,
der die gleiche Anzahl von Argumenten hat, aber uninstanziert ist.
Also ersetzen Sie diese vier Zeilen hier vorn durch diese beiden Zeilen und das hat die gleiche Funktionalität. Und hier sieht man sehr,
sehr schön, wie wir mit diesen unterschiedlichen Instanzierungsvarianten der Prädikate spielen,
wie wir plötzlich Dinge erzeugen, einer Verwendungsweise,
die eben von den klassischen, in der klassischen funktionalen Welt undenkbar ist,
dass wir eben hier eine Liste erzeugen mit einer bestimmten Länge,
dass wir hier eben Prädikate erzeugen,
mit bestimmten formalen Eigenschaften und so weiter, mit einem,
einem Prädikat, was eigentlich für den Analysezweck mal gedacht war,
ja,
gib mir den Funktor und gib mir die Stelligkeit.
So, jetzt haben wir hier einen uninstanzierten Prädikatsaufruf,
dann können wir mit diesem Prädikatsaufruf in die Datenbank gehen und sagen,
gib mir bitte,
das G2, das ist das uninstanzierte Ding,
gib mir für dieses Ziel passende Körper.
Und Moment, was ist das Pratt? Das kommt auf der
nächsten Folie. Ah ja, und den Prädikatsnamen der entsprechenden Klausel.
Also hier wird jetzt die Klausel gesucht und jetzt wird protokolliert.
Welche Klausel habe ich denn gefunden? Indem ich also die
alte Einrücktiefe nehme, wieder den Prompt davor schreibe,
dann das alte Ziel davor schreibe.
Das ist nämlich das, was hier reingekommen ist.
Das, was ich erfüllen will. Und anschließend ausgebe
den Klauselkopf, auf den das Ganze unifizieren könnte
Das ist erstmal nur Marke jetzt, sowohl das G1-Protokolliere als auch das G2.
Und das G2 sind dann jetzt sozusagen die Klauselköpfe für die Klauseln,
die alle die gleichen Funker und die gleiche Stelligkeit haben.
Dann, und jetzt kommt der entscheidende Schritt,
jetzt kann ich, nachdem ich das protokolliert habe,
kann ich sagen, okay, jetzt unifiziere ich das Ziel,
was ich habe mit dem Klauselkopf.
Jetzt wird tatsächlich die Unifikation durchgeführt, die sonst das System automatisch macht.
Hier wird unifiziert und danach kann ich sagen,
war das erfolgreich oder was ist es gescheitert
Und der Rest ist wieder dasselbe in Grün. Ich erweitere die Einrückungstiefe,
ich rufe das Ganze rekursiv auf. Also drei Schritte,
baue einen, einen uninstanzierten Aufruf zusammen, suche relevante Klauseln dafür,
protokolliere sie, dann unifiziere und protokolliere das Unifikationsresultat.
Also das mussten wir auseinanderziehen. Hier, das Suchen der
relevanten Klauseln und hier das Unifizieren des alten Teilziels mit den Köpfen der Klausel. Gut,
und das Feind Klaus ist dann also jetzt wieder der Alteaufrufsuche,
Einen Körper, der zu diesem Ziel passt,
nur ist dieses Ziel jetzt eben nicht mehr das ursprüngliche Ziel,
was mal ins System reingekommen ist,
sondern dieses künstlich erzeugte mit den Uninstanzierten Vayablen.
Also entweder ich finde diese Klausel oder aber ich finde sie
nicht, das sind die beiden Fälle hier.
Hier habe ich sie gefunden. Hier finde ich sie nicht.
Und wenn ich sie nicht finde, dann schreibe ich hin,
es gibt keine mehr. Die Datenbank ist vollständig abgearbeitet worden
. Das habe ich irgendwie hingeschrieben. Und hier habe ich
auch noch den Prädikatsnamen angegeben. Das ist eben hier dieses
vierte Element hier hinten, dass ich sage,
für diese Definition von My Last beispielsweise gibt es
keine weiteren Elemente mehr
So, und das Verhalten, was wir dann erzeugen können,
ist eben, ich kriege als erstes das Ziel,
was ich erfüllen soll.
Das ist nämlich das, was hier ins System reingekommen ist.
Und ich schaue dann die Klausel, die ich anfasse.
Und aus diesem Klauselkopf kann ich erzählen, aha,
das ist offensichtlich mein Rekursionsabschluss, der jetzt abgearbeitet wird.
Eher hier. Und dahinter kann ich sehen, der passt nicht.
Das ist gescheitert, weil ich eben hier diese einer Liste
nicht auf diese Dreierliste udifiziert.
Also kommt die Alternative zum Tragen und das ist der Rekursionsschritt.
Und das ist erfolgreich. Und dann sehe ich jetzt eben hier,
ja, offensichtlich ist jetzt hier G345 an A gebunden worden und das G346 an das BC.
Und mit dem BC wird das Ganze wieder aufgerufen.
Das ist der nächste Rekursionsschritt, deshalb die Einrückungstiefe etwas erhöht.
Ich kriege wieder den Rekursionsabschluss zu fassen, der scheitert,
ich komme in Rekursionsschritt und da geht es eins weiter.
Im dritten Schritt sehe ich jetzt, ist die Unifikation mit
dem Rekursionsabschluss erfolgreich. Es wird tatsächlich das Ergebnis ausgegeben.
Das ist wieder das Prolog-System, was sich meldet.
Das nimmt das Prolog-System auch noch entgegen und ab
sofort ist unser Meta-Interpret da wieder daran
Der guckt jetzt also noch, was passiert mit dem Rekursionsschritt? Ja,
klar, der baut wieder ein Element ab,
hat jetzt die leere Liste hier,
die leere Liste unifiziert nicht mit diesem Klauselkopf, das scheitert,
also die Alternative der Rekursion Schritt, das scheitert genauso,
weil die leere Liste hier nicht mit dieser,
mit dieser mindestens einen elementigen Liste unifiziert, das scheitert auch.
Und dann kann er mir sagen, ja,
damit habe ich hier auf der Rekursionsebene alle Klauseln abgearbeitet,
auf dieser Rekursionsebene auch,
weitere Ergebnisse gibt es nicht
An der Stelle sehen wir jetzt also tatsächlich,
was das Produx-System wirklich macht und nicht die ganzen Abkürzungen auch noch mit drin,
die uns der Tracer im System vorenthält.
Insbesondere also diese ganzen gescheiterten Aufrufe,
die werden eben hier mit protokolliert und es wird zusätzlich protokolliert,
wenn die Datenbank vollständig abgearbeitet worden ist.
Gut, das ist also ein Beispiel dafür, wie ich
dem Produx-System sukzessive mehr Dinge aus der Hand genommen habe.
Zuerst war es die Suche nach der relevanten Klausel plus Unifikation
und dafür haben wir dieses eingebaute Prädikat des Systems verwendet
Das war uns auch noch zu rigide. Da haben wir
nicht genügend viele Informationen bekommen. Also haben wir ihm auch
die Suche noch aus der Hand genommen. Und haben gesagt,
okay, wir gucken erstmal, wir suchen alle relevanten Klauseln.
Und ob sie denn erfolgreich unifizieren oder nicht, das können
wir hinterher immer noch klären. Also wir haben getrennt,
die Suche nach der Klausel von der Unifikation mit dem Klauselkopf.
Und erst bei der Unifikation mit dem Klauselkopf wird tatsächlich die Instanzierung erzeugt,
die ich im Körper der Klausel brauche, um die ganze Verarbeitung weiterzutreiben.
Also an der Stelle sieht man sehr schön,
wie man anfangen kann, die Semantik zu beeinflussen.
Allerdings haben wir die Esemantik hier ja noch nicht grundlegend beeinflusst,
sondern nur gesagt, welche Ergebnisse wollen wir tatsächlich zu Gesicht bekommen.
Die Berechnungsergebnisse, also immer dann,
wenn sich hier das Prolox-System selber wieder meldet,
das sind exakt die gleichen, die wir vorher auch hatten.
An der Stelle hat sich nichts geändert. Wir kriegen nur mehr
Informationen über den Ablauf, der zu diesen Ergebnissen geführt hat, präsentiert.
Und das ist dann der nächste Schritt, den wir gehen werden.
Und das nehmen wir uns dann auch mal fürs nächste Jahr vor,
dass wir tatsächlich Zyklenüberwachung einbauen und über den gleichen Trick,
den wir auch verwendet haben,
bei der Memoisierung in einem allgemeinen,
einer allgemeinen Grafensuche,
können wir das hier natürlich genauso machen
Dass wir einer Geschichte mitführen und immer auf der History gucken,
ob wir so ein Teilziel schon mal abgearbeitet haben oder nicht.
Und wenn das oft genug hintereinander immer wieder passiert,
dann sind wir offensichtlich in einem Zyklus drin.
Oder vermutlich. Und das ist dann der erste Schritt,
wo wir die Semantik des Prolox-Systems verändern.
Dann kriegen wir anderes Verhalten als vorher. Bis jetzt
ist das Verhalten immer noch genau das Gleiche gewesen.
Aber hier ziehen wir uns dann den Teppich unter den Füßen weg.
Jetzt geht es auch an die Kernsemantik der Abarbeitung heran.
Okay, dazu brauchen wir viel Kraft. Ich hoffe,
sie haben über die kommenden drei Wochen Zeit und sich ein bisschen auszuruhen.
Ich wünsche Ihnen ein frohes Weihnachtsfest und einen guten Rutsch
und wir sehen uns dann im nächsten Jahr wieder.