abgearbeitet, mit einem einzigen Überläufer. Das ist die Frage
der Verbindung von Bäumen mit Vajablem Verzweigungsfaktor. Das,
was wir bisher hatten, waren ja Binärbäume, die wir uns angeguckt hatten.
Das kann man natürlich erweitern auf Bäume mit Dreifachverzweigung.
Bäume mit Vierfachverzweigung und so weiter, indem man nicht zweistellige Therme nimmt,
sondern dreistellige Thermo- oder vierstellige Therme.
Wenn man aber Bäume mit Vajablem Verzweigungsfaktor hat,
dann geht das auf die Art und Weise nicht mehr.
Dann müsste man halt maximalen Verzweigungsfaktor annehmen und das ist nicht praktikabel.
Das kann man aber ganz gut mit Listen erledigen.
Und dann kriegen wir eben eingebettete Listen,
in denen wir eine Baumstruktur realisieren und die Baumstruktur wird benutzt,
um beispielsweise Wörter in einem Wörterbuch zu finden.
Das ist in verschiedener Hinsicht sehr attraktiv. Einerseits ist für typische lexikalische Bestandteile,
wie wir das in den natürlichen Sprachen haben,
sehr sparsamer Speicherplatz bedarf,
weil wir die Anfänge der Wörterbucheintragung gemeinsam verwalten können
Der wesentliche Vorteil liegt aber in der Verarbeitung dieser Strukturen,
beispielsweise alle Spracherkenner arbeiten auf dieser Basis,
weil es dann möglich ist, die Anfangsstücke der Wörterbucheintragungen gemeinsam
zu verarbeiten und nicht für jedes Wort wieder extra.
Insbesondere für Systeme mit großem Wortschatz, wie also beispielsweise der Google-Spracherkenner,
da ist das unverzichtbar. Da muss man,
ist man nicht mehr in der Lage für sehr große Wortschätze,
alle Wörter als Hypothesen durchzuprobieren und dann organisiert man das Ganze in einer solchen Baumstruktur,
damit man die Entscheidung über das Wort
möglichst weit hinaus zögern kann
Und ja, wir implementieren sowas jetzt also. Ach so,
übrigens die, ich bin mir nicht sicher,
die Google Stichwort Vorherschlagssuche geht genauso, denn sie arbeitete inkrementell.
Sie macht bereits Vorschläge, während ich die Wörter noch eingebe.
Anwendungen sind etwa die klassischen Vervollständigungs-Interfaces für Mobiltelefone.
Ich bin mir nicht sicher, wie die Swipe-Tastaturen arbeiten.
Da weiß man relativ wenig über das,
was hinter der Kulisse vorgeht. Aber ich könnte mir vorstellen,
dass auch Swipe-Tastaturen auf die Art und Weise
organisiert sind
Gut, die Idee ist also,
ich organisiere eine Listenstruktur und habe auf dem Top-Level der Liste alle Anfangsbuchstaben von A bis Z,
hinter dem A verzweigt sich das Ganze wieder,
entsprechend aller Zweitbuchstaben, die dort auftreten können,
das ist eben hier nur eine kleine Auswahl davon und danach
kann sich das wieder nochmal verzweigen und dann sieht man eben in diesem Fall hier,
ich habe das Wort alle drin und danach ist das Wort zu Ende,
das sei der Endeindikator,
die leere Liste und ich habe das Wort alles drin,
was eine Fortsetzung von alle ist
Und ich könnte jetzt natürlich statt dieses Ende Indikat,
das hier auch beliebige Informationen reinschreiben, die mit dem betreffenden Wort verknüpft ist.
Das will man ja meistens haben, wenn man ein Telefonbuch hat beispielsweise,
dass dort dann der Verweis auf die Telefonnummer drinsteht.
Gut, und solche Strukturen kann man einerseits verwenden,
wenn man sie denn hat. Hier ist ein Prädikat dafür,
das macht als Rekussionsabschluss, wenn es diese leere Liste findet,
oder sagen wir so,
wenn denn in dem Baum dieser Endemarker drin ist,
dann ist das Wort zu Ende
Andernfalls, wenn es noch Zeichen gibt, hier,
das sind ja alles immer leere Listen, wenn es noch Zeichen gibt,
sowohl im Wort als auch im Baum und das erste Zeichen
im Wort ist gleich dem aktuellen Zeichen in dem,
übrigens zur Aussprache, dass das Ding jetzt T-A-I-E geschrieben wird,
ist Absicht.
Es sollte eben eine spezielle Sorte von Baum charakterisieren,
wie es ausgesprochen wird, darüber streiten sich die Geister.
Insbesondere ist das, ich bringe es immer durcheinander,
ich weiß nicht, in welche Richtung,
also ob man es auch im englischen Tween nennt oder ob
man es Try nennt, da gehen die Meinungen auseinander.
Insbesondere streitet man sich darüber, was der Erfinder des Ganzen
ursprünglich mal als Aussprache dafür haben wollte.
Ich glaube, er wollte Tree haben. Aber da bin ich
mir auch nicht sicher. Gut, also zurück zu dem Programm.
Wenn der erste Buchstabe übereinstimmt, dann musst du gucken,
ob du den Rest des Wortes in dem Rest des Baumes findest.
Und im anderen Fall ist es egal, ob sie übereinstimmen oder nicht.
Du musst in der Liste der Alternativen Buchstaben suchen,
ob du dort dieses Wort matchen kannst.
Also dann wieder gucken, ob der erste Buchstabe mit dem
ersten Buchstaben hier in dem restlichen Tree übereinstimmt
Und wenn wir jetzt also diesen Tree hier gegeben haben,
den hier, dann können wir ihn aus der Datenbank abrufen.
Er wird an die Vayable Tee gebunden und wir können dann
diese Prozedur aufrufen, indem wir sagen,
ist das Wort also in diesem Tree enthalten?
Und die Wörter müssen jetzt natürlich als Liste von Buchstaben gegeben sein,
damit ich dort die Listenabarbeitung realisieren kann.
Ich kann natürlich das Ganze mit einem negativen Fall testen, das Wort
Aber können wir uns davon überzeugen, ist in diesem Baum nicht drin.
Das heißt, ich kriege dann auch,
wie zu erwarten war,
eine Fehler,
ein Scheitern des Prädikatsaufrufs
Ich kann natürlich auch sagen, ich habe ein Tree gegeben.
Welche Wörter sind in diesem Tree enthalten? Dann ist der Tree instanziiert,
das Wort ist uninstanziiert und dann kriege ich,
wie zu erwarten, war aufgelistet, alle Wörter,
die in diesem Tree enthalten sind.
Ich kann natürlich auch fragen, in welchen Trees ist
das Wort also enthalten? Und er wird mir sagen,
also ist in dem Tree enthalten, der diesen Pfad enthält.
Möglicherweise gibt es noch alternative Buchstaben,
aber zumindest müssen die drin sein im Tree
Und es könnte auch sein, dass es es Präfix eines Eintrags ist,
das also danach wieder noch weitere Buchstaben folgen können.
Das sind die beiden Alternativen, die ich dann bekomme.
Keine sehr sinnvolle Verwendungsweisen, aber es funktioniert auf jeden Fall.
Gut, das ist zum Thema Listen gewesen. Damit sind
wir mit dem Thema am Ende. Was jetzt kommt,
ist eher ein Schnelldurchgang durch Teile des Manuals,
damit man mal einen Überblick bekommt,
was es noch so alles gibt in Prolog,
um zu einer vollständigen, oder zu einer Programmiersprache vollständig ist sie schon,
zu einer Programmiersprache zu kommen,
mit der man tatsächlich praktische Probleme lösen kann.
Erstes Thema ist, welche Typen,
welche Datentypen haben wir und wie kann ich Typen ineinander umwandeln.
Das Zweite ist die Frage, kann ich die Instanziierung selbst in die Hand nehmen?
Das ist ja etwas, was wir bisher weitgehend dem System überlassen haben.
Dort geht es jetzt also darum zu schauen,
ob eine Variable instanziert ist oder nicht instanziert ist und dann entsprechend darauf zu reagieren.
Drittes Thema ist ein recht wichtiges, das ist die Suchraummanipulation,
das hatten wir ja schon des Öfteren,
wenn wir nicht wirklich M-zu-N-Relationen haben wollen,
sondern beispielsweise funktionale Beziehungen haben wollen, dann möchten wir gerne mal sagen,
höre nach irgendeinem gefundenen Ergebnis auf nach weiteren Ergebnissen zu suchen.
Das ist das Thema Suchermanipulation. Und dann gibt es eine große Anzahl
extra logischer Prädikate, auf die werde ich nicht eingehen.
Der gesamte Input-Output ist praktisch extra logisch. Das hat
mit Logik und logischer Ableitung nicht das Geringste zu tun,
weil der gesamte Input-Output aus der Sicht des Systems Nebeneffekte sind.
Das sind Nebeneffekte, die den Zustand des Systems ändern und
deshalb im Rahmen der Logik keine Basis haben
Dazu müssen wir unterscheiden zwischen zwei Begriffen, dem einen,
das sind die extra logischen, ich hatte schon gesagt,
Input-Output, darauf werde ich nicht eingehen.
Es ist auch der Bereich der Datenbank-Manipulation, also wenn nicht per Assert oder Retract,
etwas in die Datenbank hineinschreibe oder wieder rauslösche,
ändere ich den Zustand des Systems.
Es gibt sowas wie einen globalen Variablen-Mechanismus,
wer die letzte Übungsaufgabe bearbeitet,
einen letzten Übungszettel bearbeitet hat und bis zum Schluss durchgehalten hat,
der ist über das Prädikat Fleck gestoßen,
Fleck ist nichts anderes als eine globale Variable oder ein Interface,
um mit globalen Variablen arbeiten zu können
Und es gehört auch noch das Interface und Betriebssystem dazu,
auf das gehe ich auch nicht weiter ein.
Metalogische Prädikate sind dagegen Dinge, die tatsächlich etwas mit der Abarbeitung,
mit der Ableitung eines Logikprogramms zu tun haben,
wo es aber jetzt nicht darum geht,
das Programm selbst abzuarbeiten und laufen zu lassen auf der Maschine,
sondern wo man beobachtet, wie sich das System verhält.
Also wo man aus einer höheren Perspektive auf die Abarbeitung des
Programms schaut und schaut, was treibt das da eigentlich.
Und dazu gehört eben
beispielsweise Typinspektion
Dass man sagt, ist dieses Argument, was jetzt dort übergeben wird,
tatsächlich eine Indicer-Zahl oder ist das irgendwas anderes?
Oder dass man Typen umwandelt in andere Typen,
dass man Typen konstruiert und eben der Bereich kontrollierte Instanzierung,
dass man schaut, ist jetzt dort einen Wert gegeben,
den ich verarbeiten kann oder existiert gar keiner.
Dazu gehören auch die Trace- und die Backwerkzeuge,
mit denen man ja auch auf die Verarbeitung des Prologux-Systems draufschaut,
auch die werde ich hier nicht im Einzelnen behandeln.
Gut, die Basistypen, die wir haben,
sind also Indicer-Float, Atom on String,
mit denen sind wir ja schon in in Kontakt gekommen,
was String eigentlich genau ist.
Darüber müssen wir uns noch unterhalten. Das kommt im nächsten
. Wir können daraus andere atomare Typen ableiten,
so etwas wie das Number eben die Zusammenfassung von Integern Float ist,
dass Atomic die Zusammenfassung von String, Atom und Nummer Zahl ist und ähnliche Dinge.
Und wir sind in der Lage,
über solche Typ-Prädikate dann die Terminierungsunsicherheit einiger Prädikate,
insbesondere im Bereich der Atometik zu beseitigen,
aber immer um den Preis,
dass wir Instanzierungsvarianten verlieren
Also beispielsweise können wir eben für einen größten gemeinsamen Teiler mit
natürlichen Zahlen immer fordern, dass die Argumente X und Y
tatsächlich gegeben sein müssen und nicht, sind nicht gegeben.
Das ist Inditure-Zahlen sein müssen. Und wir sind noch genauer,
dass es nicht nur Inditure, sondern natürliche Zahlen sein müssen
und deshalb machen wir auch noch den größten Vergleich hier.
Und auf die Art und Weise kommt dann dann auf,
das ist nicht überraschend dazu,
wenn man das Ganze eben mit Nicht-Intiture-Zahlen aufruft,
dann scheitert es,
weil es bereits bei diesen initialen Typtests hier
keinen Erfolg hat
Der Datentyps dringen ist etwas,
was sich über die Zeit sehr verändert hat und immer noch nicht
einen stabilen Zustand hat in den verschiedenen Prologvarianten im originalen Edinburgh Prolog,
ist es eine Liste von Aski-Werten gewesen.
Das hat den Vorteil, dass die Implementierung vollständig in
die Logikprogrammierung zu integrieren ist. Es ist Listenverarbeitung,
wir haben gesehen, Listenverarbeitung ist der ideale Fall,
wo wir die gesamten Vorteile der relationalen Programmierung nutzen können,
nämlich absolute Richtungsunabhängigkeit.
Es ist egal, was wir instanzieren oder was wir
nicht instanzieren, es kommen immer sinnvolle Ergebnisse heraus
Das ist also sozusagen eine schöne Implementierung,
aber eben auch eine teure Implementierung.
Worin besteht der Preis, den ich dafür bezahlen muss?
Die Aski-Werte sind es nicht. Was ist das Teure?
Ja, was ist das Objekt? Ja, das,
was es eben,
also der Knoten folgt mir dann einfach
Ja, genau. Also in der Terminologie von Frau Dreschler
ist es die Kunstzelle in SE1. Es ist tatsächlich zwei,
ein Objekt mit zwei Variablen gewesen,
aber es ist immer eine zweistellige Struktur. Und diese Verkettung,
das ist das Teure, dass ich eben in intern,
wenn ich ein dreibuchstabiges, ein Dreibuchstabe Zeichenkette habe,
eben so etwas habe wie...
Ja,
das soll
ein Nil sein
Ja, das ist ja intern eine solche Struktur,
eine, ich habe hier ein A, eine Restliste,
die einen Verweis auf B enthält und eine Restliste,
die einen Verweis auf C, sorry,
auf C enthält und hier einen Verweis auf Nill enthält.
Die Verweise sind nicht das Problem,
die werden meistens direkt in die Pointe rein kodiert,
was das Teure ist, sind diese Pointer von einer Zelle zur anderen.
Das macht das Ganze extrem teuer, denn das sind typischerweise 32 Bit,
zahlen wahrscheinlich 64 bezahlen Und wenn ich die Zeichenkette A,
B, C abspeichere, brauche ich drei Bytes.
Wenn ich es als Liste abspeichere, brauche ich dann mindestens 24 Bytes.
Das macht es teuer. Deshalb also die Suche nach effizienteren Möglichkeiten,
wo man mit dem Speicherplatz nicht ganz so großzügig umgeht.
Und dann möchte man wählen können, welche Implementierung man für welchen Zweck tatsächlich benutzt.
Also in dem klassischen Edinburgh Prologs wurde also beim Einlesen mit
dem Prädikat riet, gleich als erstes die Listenstruktur erzeugt,
also in der üblichen Schreibweise, wie wir es kennen.
Ja, ich gebe also die Zeichenkette A, B,
C ein und das wird sofort als erstes in eine solche Liste umgewandelt.
Logisch, sauber, aber leider sehr ineffizient,
deshalb ist irgendwann mal im Zuge der Normierung ein separater Datentyp drin,
neben diesem Datentyp String als Liste von, sorry,
ich habe, das ist falsch, Wir müssen überall hier ändern.
Es sind nicht die Buchstaben, die dort abgespeichert werden.
Also nicht der Code auf der Speicher-Ebene,
sondern es sind die Aski-Werte, die dort abgespeichert werden.
Ja, also das wird umgewandelt in die Liste der Aski-Werte für diese Buchstaben.
Und hier steht auch immer ein Aski-Wert drin. Gut,
also ein separater Datentyp String, wo man nach außen nicht
so richtig sehen kann, was eigentlich passiert.
Man kann also Also Kennzeichen sind diese Tüttelchen, an denen
man aber nicht unbedingt erkennen kann, welchen Datentyp das Ganze hat.
Man kann aber natürlich den Typtest machen, in dem man dann fragt,
ist, bist du ein String und wenn das positiv ausgeht,
dann ist es tatsächlich ein String.
Es gab dann eine Zeit lang die Möglichkeit zwischen den beiden Interpretationen herum zu switchen.
Inzwischen in den aktuellen SWI-Prolog-Varianten ist es so, dass man
die Implementierung als Liste von Asky-Codes durch Backquots aktivieren kann.
Also wenn man das auf die einfach so im Normalfall der Konsole eingibt,
dann den Test macht, dann ist das eine Zeichenkette.
Wenn man die Backquots verwendet, ist es keine Zeichenkette mehr
Ja, dann hat es nicht mehr den Typ Zeichenkette,
sondern dann ist es tatsächlich eine Liste.
Ja, also diese Zeichenkette mit Backwoods eingegeben ist vom Typ
Liste und sieht intern so aus als Liste von Aski-Werten.
Und damit hat man die beiden Varianten jetzt parallel zur Verfügung.
Früher musste man, wie gesagt, umschalten zwischen den beiden,
nimmt man Listenimplementation oder nimmt man tatsächlich die klassische Zeichenkettennotation oder Implementierung eigentlich.
Jetzt hat man sie parallel zur Verfügung. Wenn man Zeichenketten im klassischen Sinne haben will,
also als hintereinander gefügte Bytecodes, dann bitte Doppelquots,
wenn man es als Liste von Aski-Zeichen haben will, dann mit Backquots.
Und damit hat man drei Möglichkeiten, wie man textuelle Daten
in Produktsystemen verwalten kann. Man kann es entweder als Atom benutzen,
wenn man es braucht als Identifier,
für eine Variable beispielsweise oder für eine Konstante,
für eine alphanumerische Konstante, dann sollte man keine Zeichenketten verwenden,
weil Atome natürlich einen speziellen Status haben und vom System anders verwaltet werden,
wegen ihrer Identitätseigenschaften.
Die müssen eindeutig im System sein
Zeichenketten müssen es nicht sein. Ich kann die Zeichenkette Mist an fünf Stellen
im Programm verwenden und es ist nicht die gleiche Implementierung im System.
Wenn ich das Atommist verwende, dann ist es immer das gleiche Atom.
Das ist systemweit eindeutig dann. Ich kann es als Liste von Asky kurz verwenden,
immer dann, wenn ich an die Struktur irgendwann mal ran will.
Wenn ich also irgendwann mal diese Zeichenkette hier zerlegen will in,
oh, ich soll die Zahl A mit der Zahl B
addieren oder ich soll die Zeichenkette A,
mit der Zeichenkette B verketten oder was auch immer die Bedeutung sein sollte.
Aber das erfordert es, dass ich dies als strukturierten Ausdruck zerlege in seine Bestandteile,
dann irgendeine Operation darauf ausführe, um ein Ergebnis zu bekommen.
Wenn ich das irgendwann mal machen will,
dann sollte ich die Variante als Listen von Asky kurz verwenden.
Und in allen anderen Fällen sollte man es drinks verwenden,
weil die eben billig und problemlos zu verarbeiten sind.
Aufs Drinks ist eine strukturelle Analyse schon wieder aufwendiger,
weil ich dann die Strings mit Spezialprädikaten,
kommen wir gleich dazu,
erstmal zerlegen muss in ihre Bestandteile,
um dann irgendetwas damit anzufangen
Es gibt verschiedene Strukturtypen. Das Einfachste ist, compound,
compound ist alles, was nicht atomar ist,
also im Wesentlichen Therme mit Funkler und Argumenten.
Spezialfälle davon sind natürlich die Liste, wobei es List immer nur testet,
ob es oben auf der obersten Ebene ein gepunktetes Paar ist,
während Populist wie auch in Scream schaut,
ob die Liste bei Brekkosiven Abbau tatsächlich bis auf eine leere Liste reduzierbar ist.
Und SZ ist dann eine Test,
ein Test auf Listeneigenschaft, aber es dürfen keine Elemente
doppelt drin vorkommen
Es gibt Möglichkeiten der Typ-Konversion im Bereich der Arithmetik,
ist es beispielsweise die Umwandlung zwischen verschiedenen Zahlendarstellungen,
also zwischen Float und Inditor-Zahlen beispielsweise.
Wir hatten ja auch die Rationalzahlen schon mit uns angeguckt.
Wir können Atome, Zeichenketten und Strukturen ineinander umwandeln,
indem wir etwa ein Atom an Atomcodes übergeben und
wir kriegen eine Liste von Einzelzeichen zurück
Wird es zerlegt. Oder wir kriegen eben im allgemeineren Fall,
wenn dir ein Atom oder eine Zahl übergeben,
dann kriegen wir auch eine Liste von Einzelzeichen zurück.
Auf die Art und Weise können wir also beispielsweise auch Zahlen zerlegen und manipulieren.
Auf der Basis ihrer Dezim Alt stellen und nicht auf der Basis ihrer Bit-Repräsentation, die sie haben.
Wir können uns ein Atom auch zerlegen lassen in seine Asri-Werte,
wir können, ach so, nee,
das ist ein, sorry, ein einstelliges Atom,
ein ein buchstabiges Atom und kriegen den Assi-Wert dafür heraus,
wir können eine Intage-Zahl in einer Atom umwandeln,
wann immer wir das machen müssen,
das ist eine Frage.
Wir können ein Term in einen Atom umwandeln, noch kurioser.
Wir können Zeichenketten in Atomen umwandeln und wir können uns die
Zeichencodes für die Einzelbuchstä, in einer Zeichenkette ausgeben lassen,
in Form einer Liste wieder.
Also da gibt es einen reichhaltigen Zoo von verschiedenen Konversionsfällen.
Hier nur ein paar Beispiele für die Listenbearbeitung.
Wir können also beispielsweise mit String-to-List diese Zeichenkette,
die ja wegen der Doppelquote in Zeichenkettenimplementierung verwendet wird,
in eine Liste umwandeln lassen
Von Einzelzeichen und wir können anschließend uns die Aski-Werte dieser Einzelzeichen in die Liste,
in einer Liste aufsammeln lassen und kriegen dann eben für diese drei Buchstaben A,
B, C die Liste ihrer Aski-Werte heraus.
Und wir kriegen auch, ach so, ja, das ist,
sorry, das macht dieser Teil hier bereits und wir,
nein, so rum ist es.
Danach ist L instanziert und wir können aus der Liste dieser Aski-Werte
uns das Atom generieren lassen und kriegen dann Das in dieser Backquote-Notation zurückgegeben.
Das ist jetzt ein String-in-Listen-Notation. Das ist nur eine
einfache Liste, aber das ist ein String-in-Listen-Notation.
Wir können uns ein Term in einen Atom umwandeln lassen.
Wir können uns auch da wieder das Atom in eine Liste
zerlegen lassen und können anschließend die Liste wieder in einen Atom umwandeln.
Wir starten also mit dieser Ja, mit der Backquoten-Notation,
die Backquoten-Notation ist ja die Listendarstellung des Trinks.
Wir wandeln das dann in die Liste der Asky-Werte um und
anschließend wieder in ein Atom und kriegen dann, also umgekehrt,
wir haben das A gegeben und wollen uns aus dem Atom
den Zeichenkette erzeugen lassen und kriegen dann die Zeichenkette in der Zeichenkettenimplementierung zurück.
Also auf die Art und Weise kann man jetzt beliebig
zwischen den verschiedenen Repräsentationen hin und her rechnen.
Man muss nur immer wissen, wovon redet man gerade,
welche Implementation einer Zeichenkette liegt hier vor
Das ist vielleicht ein bisschen verwirrend,
weil es die Listen-Implementierung in anderen Programmiersprachen natürlich nicht gibt,
dass es speziell für Prolog, glaube ich,
jedenfalls das Spezielle ist, ja.
Ja, man auch so kommt zwar bei mir in das Synchronikation, die man dann ausführen.
Ja, sicher, ja, da kommen wir noch dazu.
Das ist ja Meta-Programmierung, ja, genau. Das ist,
da wollen wir aber typischerweise keine Zeichenketten erzeugen,
sondern da wollen wir Therme haben,
denn die können wir direkt als Ziel verwenden,
die können wir direkt in die Datenbank schreiben und als Programm abarbeiten.
Also da geht es dann darum,
komplexe Therme zu erzeugen und abzuarbeiten
Ja, für bestimmte Typen haben wir Typ spezifischer Zugriffsfunktionen.
Das ist überall in allen Programmiersprachen verfügbar. Wir können die Länge
eines Atoms uns geben, lassen die Länge einer Zeichenkette.
Wir können uns Teile einer Zeichenkette rausschneiden lassen mit einem Subsubstring-Prädikat.
Für Listen kennen wir das bereits alles. Wir können uns
das letzte Element einer Liste geben lassen. Wir können testen,
ob ein Element in der Liste enthalten ist und so weiter.
Wir können auch Typen konstruieren,
indem wir beispielsweise eben ein Atom in eine Liste von Einzelzeichen
umwandeln oder eben eine Zahl in eine Liste von Ziffern.
Wir können Atome miteinander verketten,
indem wir einen Atom geben und ein zweites Atom geben und
ein drittes und die Verkettung der Atome daraus bekommen.
Wir sehen an den Fragezeichen, es kann ein Beliebiger Instanzierung auch aufgerufen werden.
Wir können es also auch benutzen, um Atome zu zerlegen.
Und schließlich können wir auch eine Liste von Atomen
hier übergeben und wir kriegen dann eine Verkettung aller Atome in
der Liste als ein Gesamtatom heraus
Ein relativ wichtiges Prädikat, wo wir noch verwenden werden,
ist die Umwandlung von Termen in Listen.
Das mag ein bisschen überraschend sein, erstmal, weil ja
Listen, Therme sind. Also diese zweistelligen Strukturen sind ja
nichts anderes als spezielle Formen von Termen im Prolog-System.
Hier ist eine andere Umwandlung gemeint, die eine Liste so interpretiert,
dass das erste Element der Funktor eines Terms ist und die
restlichen Elemente der Liste sind die Argumente des Terms.
Also wenn ich sage,
wandle mir diesen Term in eine Liste um,
dann kriege ich eben den Funktor als erstes Listen-Element und das
erste Argument als zweites Listen-Element und das zweite Argument als
drittes Listen-Element
Und das geht natürlich für beliebigstellige Ausdrücke,
denn Listen sind ja geduldig und können beliebig lang sein.
Ich kann das auch in beiden Varianten aufrufen,
also sowohl Therme in Listen umwandeln als auch Listen in Therme umwandeln.
Ich kann auch sagen,
nimm diese Liste hier und gib mir einen Therm zurück,
wo der Funktor das erste Element der Liste ist und
die restlichen Elemente der Liste die Argumente des Therms sind.
Das ist die einzige Möglichkeit und das ist der Bezug zur Meta-Programmierung,
dafür brauchen wir das. Das ist die einzige Möglichkeit zu bestimmen,
welches Atom hier vorn als Funktor verwendet wird. Eine andere Möglichkeit gibt,
sorry, wir werden gleich noch die nächste kennenlernen,
aber Normalerweise sagen wir es so,
normalerweise per Unifikation kommen wir ja nicht an den Funktor heran.
Das Einzige, was wir tun können in der Unifikation,
ist passiv zu prüfen, ob der Funktor identisch ist.
Aber wir können ihn nicht manipulieren. Das ist eine Möglichkeit,
um ihn zu manipulieren. Die zweite Möglichkeit kommt gleich.
Wir können inspizieren, ob die Variablen instanziert sind oder nicht.
Wenn ich also aufrufe, ist X ein Variabler Ausdruck,
dann kriege ich Ja heraus, wenn ich hier eine Konstante eingebe,
kriege ich Falls heraus.
Es gibt das Gegenstück dazu, nonvar, wenn ich frage,
ist diese Variable nicht vaabel, dann kriege ich Falls heraus.
Und wenn ich frage, ist diese Konstante nicht vaabel,
kriege ich Truhe heraus
Wesentlich ist, dass Ausdrücke die Variablen enthalten,
also Therme, die noch uninstanzierte Variablen enthalten.
Nichtsdestotrotz keine Variablen sind. Der gesamte Therm ist eine Konstante.
Auch wenn er partiell nicht instanziert ist. Also diese Variable
X hier hat keinen Wert, was wir hierran sehen.
Aber trotzdem ist die Antwort auf die Frage, ist,
dass eine nicht Variable Ja. Also auch wenn sie noch Variablen enthält.
Wir können fragen, ob ein Therm einen grauen Term ist,
also ob er noch freie Variablen enthält oder nicht.
Und dann kriegen wir für wenn er Variablen enthält oder
wenn er eine Variable ist, hier enthält er Variablen,
ihr ist der eine Variable, kriegen wir immer Fals.
Das sind keine Grundtherme. Ein vollständig instanzierter Therm ist natürlich ein Grundtherm.
Der enthält keine Variablen mehr und dann kriegen wir Truhe heraus.
Auf die Art und Weise können wir testen,
ob in einem einer Struktur noch uninstanzierter Elemente drin sind.
Das können wir jetzt benutzen, um wenigstens einige Prädikate partiell relational zu machen.
Also beispielsweise können wir eine relationale Addition implementieren, Allerdings ist
das Ergebnis nicht vollständig relational, sondern nur partiell relational.
Also wir machen die Einschränkung, dass von den drei Argumenten
wenigstens zwei instanziert sein müssen. Ansonsten wird es schwierig.
Also wir testen im ersten Fall in der ersten Klausel,
wenn X und Y gegeben sind, dann ist es die klassische Addition.
Z wird als Summe von X und Y berechnet.
Wenn X gegeben ist und Z gegeben ist,
dann ist es eben die Differenz von Z minus X.
Und dann kann ich das Y berechnen, was nicht gegeben ist.
Und wenn denn Y und Z gegeben sind,
X ist nicht gegeben, kann ich X als Differenz aus
Z und Y
Dann kann ich wenigstens die Addition relational verwenden,
solange es wenigstens zwei installierte Argumente gibt.
Wenn nur ein Argument instanziert ist, wird es schwierig,
aus dem einfachen Grunde, weil ich dann anfangen müsste, Alternativen aufzuzählen.
Dann müsste ich Zahlen generieren und das ist natürlich von Annahmen über
den Zahlenbereich abhängig. Für Inditure-Zahlen geht es in Spezialfällen noch,
wenngleich oftmals das Ergebnis unendlich viele Zahlen bedeuten wird,
also zahlen Paare, kriege ich ja dann als Ergebnis heraus.
Wenn ich wissen will, was muss ich miteinander addieren,
um zwei herauszubekommen und das für Inditure-Zahlen machen,
gibt es natürlich unendlich viele Lösungen dafür
Für sowas wie Gleitkommazahlen geht es natürlich überhaupt nicht. Da versagt das dann vollständig. Gut,
das ist der Aufruf des Ganzen,
also 3 plus 4 ist natürlich gleich 7. Was muss ich zu 1 addieren,
um 4 herauszubekommen, ist 3, was muss ich,
wie kann ich 7 in zwei Semanten zerlegen und dann kriege ich Falls heraus,
weil das die Bedingung verletzt,
dass mindestens zwei Argumente instanziert sein müssen.
Es gibt eine Reihe von Vergleichen,
die dann aber sehr speziell sind und ich glaube nicht,
dass einem das über den Weg läuft.
Also mir kommt es ganz, ganz selten unter,
dass man sowas mal tatsächlich für irgendeinen sinnvollen Zweck verwenden kann, aber es existiert.
Wir haben ja bisher immer als einzige Möglichkeit für den Vergleich von Thermen die Unifikation,
kennen wir, wir prüfen den Funktor auf Identität,
wir prüfen die Schnelligkeit, wir unifizieren die Argumente rekursiv.
Es gibt schärfere Formen des Thermvergleichs, der Schärfste,
den wir haben, ist die strukturelle Identität,
strukturelle Identität fordert erstmal auch gleich ein Typ,
gleichen Funktor,
gleiche Stelligkeit,
das kommt uns bekannt vor und dann aber nicht unifizierbare Argumente,
sondern gleiche Argumente
Und insbesondere bei den Valiablen, es ist sehr,
sehr strikt, wenn ich Valiablen mit unterschiedlichen Namen habe,
dann ist dies bereits ein Grund, die Identität abzulehnen.
Also der TMA von X ist nicht gleich dem TMA von Y,
weil er sich in diesem Valiablen Bezeichnern unterscheidet.
Wenn ich vorher die beiden Valiablen mal unifiziert habe,
dann sagt er plötzlich, sie sind strukturell identisch.
Nach Unifikationen sind sie ja durch Cho-Referenz miteinander verbunden und
das ist in dem Sinne dieser Gleichheitsdefinition dann
eine Identität
Gut, also das ist das,
wie wir es auch von der Unifikation kennen.
Hier ist es nicht der Fall, da ist der Typ nicht gegeben.
Wir haben hier eine Variable und hier eine Konstante gegeben.
Die Unifikation würde sagen, wir setzen die Variable auf diesen Wert.
Die strukturelle Identität sagt, das sind zwei völlig verschiedene Dinge.
Damit kann ich nichts anfangen. Strukturen, die gleichen Funktor,
gleiche Stelligkeit und identische Argumente haben, sind natürlich insgesamt identisch.
Das haben wir bereits diskutiert. Ach so, ja,
zwei Strukturen. Die gleiche Variablen enthalten sind ebenfalls identisch.
Aber eben, wenn sie unterschiedliche Variablen enthalten,
Variablen mit unterschiedlichem Namen enthalten,
dann sind sie nicht identisch
Der zweite Begriff ist die strukturelle Gleichheit,
ist wie ein Identitätstest, aber etwas weiter aufgeweicht.
Sie sind auch gleich, wenn sich bei konsistenter Umbenennung gleiche
Koreferenzbeziehungen ergeben. Der wesentliche Punkt ist also hier hinten,
beispielsweise hier, wenn ich die Variablen B konsistent umbenenne,
also ich benenne B um und die,
dann werden die Chorreferenzbeziehungen davon nicht betroffen.
Egal ob ich A oder B umbenenne. Die gleiche Chorreferenz,
die hier zwischen ersten und zweiten Argument besteht,
ist auch nach Umbenennung immer noch erhalten geblieben.
Deshalb ist das Ganze strukturell gleich
Gleiches gilt hier, wenn ich hier konsistent umbenenne,
ist die nicht Co-Referenz ebenfalls erhalten geblieben und deshalb ist das Ganze wahr.
Hier ist es tatsächlich falsch, weil Ja,
weil ich durch Kurzeninzentrum mitsinne, die Chor-Referenzbeziehung ändern kann.
Ich muss auch immer erst mal nachdenken. Es gibt dann im Endeffekt diese Hierarchie,
die Unifizierbarkeit ist der schwächste Vergleich für Therme, die wir haben,
strukturelle Identität ist der schärfste und die strukturelle Gleichheit ist irgendwo dazwischen.
Aber das ist ein typischer Fall, wo man ins Handbuch gucken muss,
um noch mal genau zu verstehen, was tue ich hier eigentlich, was passiert hier.
Wir brauchen oftmals, das werden wir spätestens dann haben,
wenn wir Metaprogrammierung betreiben und beispielsweise Die Suche selbst in die Hand nehmen wollen,
wollen wir entscheiden wollen,
ob wir jetzt ein Teilziel in die Suche geben oder nicht oder ob wir vorher noch irgendetwas damit machen,
dann müssen wir oftmals schauen, ob es beispielsweise eine Datenbank-Eintragung gibt,
die unifizieren würde, aber wir müssen verhindern,
dass tatsächlich unifiziert wird,
denn die Unifikation stellt sofort und unwiderruflich vajablen Bindungen her.
Wenn wir das vermeiden wollen, brauchen wir oftmals einen Term,
der Strukturgleich ist, aber uninstanzierte Variable enthält.
Und das kann man etwa durch Copithermen erzeugen.
Die Unifikation stellt ja sofort eine Chorefanzbeziehung her zwischen Thermen.
Und wenn ich das verhindern will,
wenn ich also tatsächlich einen frischen Thermen generieren will,
dann kann ich das mit Copithermen machen.
Das werden wir in der Meta-Programmierung tatsächlich benötigen.
Wir können uns auch über das Prädikat freie Variablen
eine Liste aller freien Variablen in einem Thermen erzeugen lassen,
wenn wir etwa die vaiablen Bindung selber in die Hand
nehmen wollen
Wenn wir also dem System das Privileg der Unifikation wegnehmen wollen,
dann können wir das machen, indem wir uns die Liste
der Freien Valiablen geben lassen und die in geeigneter Form manipulieren.
Möglicherweise nur, um sie auszudrucken und dann hinterher die normale Verarbeitung fortzusetzen.
Aber auf jeden Fall, das sind Dinge,
wo wir in den Ablauf der Ableitung eingreifen können und uns
Details aus dem Ableitung, aus der Ableitungsgeschichte anzeigen lassen können.
Jetzt kommt die alternative Variante, die ich vorhin angekündigt hatte.
Man kann aus, wenn ich eine Struktur erzeugen möchte,
kann ich das eben dadurch machen, dass ich sage,
X, Soll aus der Liste A, B, C erzeugt werden.
Und dann kriege ich für den Wert von X
die Struktur A von B, C heraus.
Ja, das hat mir vorhin. Da habe ich gesagt, okay,
das ist die einzige Möglichkeit, den Funktor hier vorn zu kreieren.
Es ist nicht die einzige Möglichkeit, die zweite kommt jetzt.
Ich habe das Prädikat Funktor, was mir einen Term nimmt,
den Funktor liefert und die Stelligkeit des Terms liefert.
Und das kann ich wieder in allen Instanziierungsvarianten aufrufen. Das heißt,
ich kann einen Funktor, einen Term zerlegen,
in seinen Prädikatsnamen und die Stelligkeit, also das ist in diesem Falle A,
als Funktor hier vorn und die Stelligkeit ist einstellig.
Oder ich kann sagen, ich möchte einen zweistelligen A-Term haben.
Und dann kriege ich einen zweistelligen A-Term erzeugt mit dem
Funktor A und zwei Argumenten mit uninstanziierten Variablen drin.
Auf die Art und Weise kann ich jetzt also tatsächlich
mir Strukturen erzeugen lassen und ich kann insbesondere hier vorgeben,
wie der Funk da heißen soll.
Es ist also eine zweite Möglichkeit,
eine Struktur zu erzeugen mit einem freigewählten Funktor durch das Programm selber
Als Programmierer kann ich es natürlich sowieso immer irgendwo reinschreiben,
aber wenn ich es dynamisch erzeugen will,
dann auf die Art und Weise.
Das werden wir tatsächlich auch in der Metaprogrammierung brauchen.
Wir können dann auch einzelne Argumente gezielt instanzieren. Wir sehen also,
das sind alles Funktionalitäten, die bisher das Privileg der Unifikation waren.
Die Unifikation hat dafür gesorgt, dass Argumente instanziert werden.
Nicht wir. Jetzt über die Prädikate können wir es selber
in die Hand nehmen. Wir können also beispielsweise sagen,
ich habe hier einen Term und das erste Argument dieses Terms
hat diesen Wert
Und auch das geht wieder in allen Richtungen zu verwenden. Ich kann also beispielsweise sagen,
liefer mir eine zweistellige Struktur mit dem Funktor A,
das ist genau das, was wir hier auch schon mal gemacht haben.
Da kriegen wir das als Ergebnis, A,
von einer Valiablen und einer zweiten Valiablen. Und dann können wir sagen,
gib mir bitte dein Instanziere, die erste Valiable, Quatsch,
instanziere das erste Argument dieses Terms mit dem Wert B und instanziere das zweite Argument dieses Terms,
der hier erzeugt worden ist mit C. Und dann wandelt dies diesen zweistelligen A,
Ausdruck, mit uninstanzierten Vayablen, in einen fallständig instanzierten Thermen um.
Wobei ich sehr gezielt einzeln sagen kann, instanziere hier so,
instanziere hier so. Also das ist alles Basisfunktionalität,
die uns im Standardfall das System zur Verfügung stellt und auch das Privileg dafür hat.
Aber es gibt immer die Hintertür, das selber in die Hand zu nehmen.
Damit kommen wir zur Suchermanipulation. Die Möglichkeit, dem System zu sagen,
ob es bestimmte Suchalternativen noch berücksichtigen soll oder Eigentlich,
um ihm zu sagen, dass es dies nicht tun soll.
Ich kann es, das ist die typische Verwendungsweise,
dazu benutzen, ein eindeutiges Berechnungsergebnis zu erzwingen.
Man sollte eigentlich genauer sagen, ein eindeutiges Berechnungsergebnis.
Es gibt dann eben, egal in welcher Instanziierungsvariante ich das
betrachte, immer nur ein Ergebnis zu sehen.
Dafür gibt es ein spezielles, nullstelliges Prädikat.
Das ist dieses Ausrufezeichen oder typischerweise als Cut bezeichnet,
weil es eben Teile des Suchraumes wegschneidet.
Daher kommt der Name. Die Semantik ist ein bisschen kompliziert
Man kann es einfach beschreiben, die Konsequenzen sind aber nicht
so ganz so einfach zu übersehen. Die Semantik ist hier
angegeben, es legt die Suche auf eine Auswahl fest.
Also Auswahl heißt, eine valiablen Bindung fest,
die seit der Unifikation des Klauselkopfes entstanden ist, in der der Cut auftritt.
Und das hat zwei Konsequenzen. Also erstens das Ganze ist
immer erfolgreich, das scheitert nie, der Cut.
Wegschneiden geht immer. Auch wenn nichts da ist,
wenn zum Wegschneiden, den Fall werden wir uns gleich angucken müssen,
wenn nichts da ist, zum Wegschneiden ist das auch erfolgreich.
Aber wenn etwas weggeschnitten werden kann, dann wird es auch weggeschnitten.
Gut, also die Frage ist, was hat das
für Konsequenzen
Festlegen auf eine Auswahl, die seit der Unifikation des Klauselkopfes entstanden ist.
Das hat zwei Dinge. Links von dem Cut könnten Teilziele stehen,
die alternative Ergebnisse produzieren können. Die werden durch den Cut weggeschnitten.
Das ist Punkt Nummer eins. Es könnte aber auch sein,
dass es eine alternative Klausel gibt, die Ergebnisse produzieren könnte.
Diese alternativen Klauseln werden ebenfalls unsichtbar in dem Augenblick,
wo der Cut verarbeitet wird. Es hat also zwei Wirkungen
und das muss man immer genau auseinanderhalten. Also erstens,
Alle Suchpfade verschwinden, die sich durch alternative Klauseln für das Prädikat ergeben.
In dem Augenblick, wo der Cut verarbeitet wird.
Also wenn vorher schon drei Klauseln erfolglos geprüft worden sind und
in der vierten Klausel wird der Cut verarbeitet,
dann fällt Klausel 5, 6, 7 und 8 weg.
Nicht die davor, das ist ja bereits passiert.
Die hat das System ja schon angeguckt. Also alle die,
die nach dem, nach der Klausel, die den Cut enthält,
in der Datenbank auftreten, die sind plötzlich nicht mehr sichtbar.
Und die zweite Variante, alle Alternativen, variablen Bindungen,
die in Teilzielen links vom Cut entstanden sein könnten,
die verschwinden ebenfalls
Also Backtracking in Teilziele, die Links vom Cut erstehen,
ist dann also nicht mehr möglich. Und alle Klauseln,
die in der Datenbank auf den verarbeiteten Cut folgen,
die sind für die weitere Verarbeitung unsichtbar.
Hier ein Beispiel, rein völlig günstliches Beispiel.
Wir haben eine Reihe von Klauseln, zwei Regeln.
Und wir haben eine ganze Reihe von Fakten,
wenn wir das Ganze jetzt mit dem Ziel A von X aufrufen,
nehmen wir als allererstes die Regel.
Und substituieren X mit X1, müssen den Körper abarbeiten,
rufen wir als erstes B zweistellig auf,
da haben wir zwei Fakten hier.
Das heißt,
es gibt diese Verzweigung
Wir müssen immer noch das zweite Teilziel abarbeiten. Das zweite
Teilziel ist in SC. Für C gibt es auch zwei
Alternativen. Deshalb teilt sich der Suchraum hier nochmal auf.
Und wir haben dann die Varianten C von B,
das ist erfolgreich, C von C. Warum?
Achso, ja. Das Teilziel ist ja C von B,
die Unifikation mit C von C, was in der Datenbank steht,
fehlt, schlägt Fehl. In dem alten,
anderen alternativen Zweig kommen wir auf das gleiche Teilziel,
also das gleiche Ne, nicht das gleiche Teilziel.
Hier drin steht nur statt des BSS C. Deshalb scheitert diese Unifikation,
aber diese ist erfolgreich. Und hier hinten in dem zweiten Zweig,
das ist die zweite Klausel, rufen wir nur D von X1 auf.
Das können wir entweder mit D oder mit E instanzieren,
dieses X1. Und das heißt, es gibt nochmal zwei erfolgreiche Ergebnisse.
Also insgesamt vier Ergebnisse, die wir hier haben.
Jetzt bauen wir hier einen Cut ein in dieses Prädikat.
Und das heißt, in dem Augenblick, wo der Cut
hier verarbeitet wird, nachdem dieser Teilzweig hier erfolgreich abgearbeitet wurde.
Also ich habe für das X1 Y1 Einen Aufruf erzeugt,
ich habe es mit diesem Fakt instanziert und habe hier ein erfolgreiches Ergebnis erzielt,
dann muss ich ja hier die zweite Klausel,
die hier noch abzuarbeiten ist, aktivieren.
Dazwischen kommt aber der Cut. Dann dieser Cut macht alle
weiteren Klauseln für das aktuelle Teilziel unsichtbar.
Das heißt, diese Klausel existiert nicht mehr,
weshalb der gesamte Rechte Zweig des Suchraumes weg ist.
Das ist die erste Wirkung
Und die zweite Wirkung ist, ich habe hierfür,
für das B, ein Ergebnis erzielt.
Es werden alle Alternativen Links davon weggeschnitten. Also mit der
Klausel habe ich das Ergebnis erzielt. Diese Klausel existiert nicht
mehr, denn sie würde mir ein alternatives Berechnungsergebnis erzeugen.
Mit der ersten Klausel komme ich zu diesem Erfolg hier,
aber ich muss natürlich dieses Ergebnis noch an der zweiten,
an einem zweiten Teilziel überprüfen.
Und bevor das passiert, werden alle alternativen Möglichkeiten weggeschnitten.
Das heißt, diese zwei, Quatsch, Doch, ja.
BVC, dieser Zweikehr. Dieser Zweig ist nicht mehr begehbar.
Die Variante B von AB, die ist gewendet worden.
Die Variante B von BC ist nicht mehr zugreifbar, weil ein zweiter,
ein Reh-Duo in dem Sinne kann man es auch sagen,
das Reh-Duo für dieses B zweistellig, das wird nicht mehr gemacht.
Nachdem das erste Ergebnis produziert wurde, ist das System zufrieden damit. Gut,
also diese beiden Verwendungsweisen, entweder alternative vaiablen Bindungen für Teilziele links
vom Cut verschwinden oder aber alternative Klauseln für das aktuelle Teilziel,
was bearbeitet wird, und damit ganze Zweige im,
eine Etage höher im Suchraum werden ebenfalls weggeschnitten.
So, mir Hinblick auf die Pragmatik gibt es zwei Fälle zu unterscheiden,
die visuell gut ausgewählt als Greencut oder Redcut bezeichnet werden.
Greencut sind die, die keine Änderung der Semantik des Programms
hervorrufen, die ich nur verwende,
um eine Effizienzsteigerung hervorzurufen
Also in Fällen, wo ich weiß,
dass es eindeutiges Ergebnis nur geben kann,
und ich breche nach dem ersten Ergebnis ab,
das ist ein Greencut. Also beispielsweise in unseren Arrithmetikprogrammen,
wo wir nach dem Finden des ersten größten gemeinsamen Teils abbrechen,
weil wir nicht wissen, weil wir genau wissen,
einen zweiten wird es nicht geben.
Oder wenn wir irgendwelche Tests machen und über den Rekursionsabschluss hinweg iterieren könnten,
können wir an der Stelle den Cut setzen und dann verhindert es,
dass wir ein Terminierungsproblem an der Stelle bekommen
Er Red Cut hingegen ist der, wo wir aufpassen müssen,
weil er eine Veränderung der Programmsemantik mit sich bringt.
Green Cuts sind willkommen, Red Cuts sind problematisch.
Die Unterscheidung zwischen den beiden sind, ist teilweise etwas schwierig.
Wir können also beispielsweise uns ein Prädikat definieren, was für
zwei gegebene Zahlen in diesem Falle uns das Maximum liefert.
Und definiert eben dafür, wenn X größer ist als Y,
dann ist X das Maximum,
wenn Y größer oder gleich X is,
dann ist Y
das Maximum
Einfaches Prädikat. Wir wissen ja, dass durch diesen Größenvergleich
der Raum der Möglichkeiten exhaustief beschrieben ist. Weitere Fälle gibt es
nicht. Das heißt, wenn das Erste erfolgreich war,
muss das Zweite, die zweite Klausel immer scheitern.
Es geht nicht anders. Wir können also,
nachdem wir festgestellt haben, die erste Klausel ist erfolgreich,
den Cut setzen, was heißt,
die zweite Klausel wird unsichtbar und wird nie wieder angefasst.
Dadurch verhindern wir, dass er Umsonst nochmal die zweite Klausel aktiviert,
was ja mit gewissem Aufwand verbunden ist.
Es muss der Klauselkopf unifiziert werden. Es muss ein Stack-Eintrag
angelegt werden und so weiter. All das sparen wir uns,
wenn wir hier mit dem Cut das Ganze bereits wegschneiden.
Das ist ein typischer Fall eines Screencuts. Er ändert an
der Programmsemantik nichts. Das Verhalten ist exakt genau das Gleiche.
Was wir gewonnen haben, ist ein kleiner Rationalisierungseffekt.
Dann können wir mutig werden und können sagen, ja,
wenn das so ist, ja, brauchen wir dann die zweite Klausel überhaupt noch.
Wir können ja dann sagen, wenn das erste erfolgreich war,
schneiden wir das Ganze weg.
Ansonsten können wir blindlings sagen, es ist Y-Dings das Größere
der beiden
Ja, warum eigentlich nicht? Seht doch gar nicht schlecht
aus. Das Problem ist, auf die Art und Weise
verlieren wir eine Instanzierungsvariante. Wenn wir jetzt testen,
ist, was ist von drei und zwei das Größere?
Dann kommen wir in die erste Klausel.
Wir stellen fest, drei ist größer als zwei.
Wir schneiden alles weg und das sind zufrieden. Das ist
das Ergebnis, was wir haben wollten. Jetzt fragen wir,
ist drei die größere der beiden Zahlen 3 und zwei,
dann wird das zu drei, das zu zwei,
das wird zu drei, drei ist größer als zwei,
alles in Ordnung.
Weitere Alternativen überprüfen wir nicht,
das ist das,
was wir haben wollen
Jetzt testen wir,
ist die zweite größere von den beiden Zahlen 3 und 2,
dann haben wir für X die 3,
nach wie vor für Y die 2. Moment,
was habe ich jetzt gemacht?
Achso, ja, und dann für X die Hard.
Für X haben wir ja den Wert vorgegeben.
Dadurch haben wir hier eine Chorreferenzforderung zwischen 3 und 2,
die scheitert natürlich. Also für diesen Aufruf scheitert bereits
die Unifikation mit dem Klauselkopf
Das heißt, ich muss jetzt,
ohne dass ich hier im Körper überhaupt etwas angeguckt habe,
schon gar nicht diesen Cut hier hinten,
gehe ich in die zweite Klausel und sage, blind links,
ja, dann ist es wohl offensichtlich die zweite,
das zweite Dings.
Und wir sehen, hier wird nur Co-Referenz zwischen zweiter und dritter Argumentstelle gefordert,
die ist gegeben, wir kriegen ein Truh raus,
was natürlich definitiv falsch ist.
Das Interessante ist hier, dass ausgerechnet die,
bisher hatten wir ja immer das Problem,
dass wenn wir etwas vollständig instanziert aufrufen,
dass es
glatt gegangen ist
Und in dem Augenblick, wo wir Vegablen hinzubekommen,
dann kriegen wir plötzlich Terminierungsprobleme und so weiter und so fort.
Das ist bisher die typische Situation gewesen. Jetzt ist es umgekehrt,
solange wir das mit einer Variablen aufrufen, ist alles in Ordnung.
In dem Augenblick, wo wir es vollständig instanziert aufrufen,
das ist ja der Fall unserer relationalen Datenbank praktisch.
In dem Augenblick bricht das Ganze zusammen. Da kriegen wir
falsche Ergebnisse. Das, was wir hier haben,
ist also ein Redcut und wir müssen eigentlich das Prädikatsschema korrigieren,
was uns hier noch gesagt hat,
wir dürfen das Maximum vorgeben oder auch nicht vorgeben,
müssen wir jetzt tatsächlich scharf verbieten.
Es darf nicht
instanziert sein
Die Instanzierung des dritten Arguments führt zu einem Fehlverhalten. So,
und das müssen wir, wir kommen dann gleich drauf, dann noch vergleichen mit dem FNLs.
Also das ist keine gute Implementierung, die auf der vorhergehenden Folie,
ich brauche tatsächlich die zweite Klausel mit diesem Guard hier hinten drin,
der mir sicherstellt, dass das nur angewendet werden kann,
wenn tatsächlich Y größer als X ist, nicht im umgekehrten Falle.
Das war ja gerade das, was wir hatten,
dass diese Bedingung hier verletzt war. Gut, Ja,
ein Beispiel hatten wir schon mal diskutiert, in dem Augenblick,
wo wir wissen, dass sich eindeutig die Ergebnisse haben,
dann können wir auch den Cut verwenden. Also beispielsweise,
wenn wir wissen, dass eine Mutter immer eindeutig gegeben ist,
in der Datenbank können wir verhindern,
dass vergeblich nach zweiten Müttern gesucht wird,
nehmen damit aber in Kauf, dass der relationale Charakter verloren geht,
die Einschränkung ist dann in beiden Richtungen gegeben,
wir können nicht mehr die Mütter aufzählen.
Das geht nicht mehr, weil wir nach der ersten Mutter
zwangsweise immer aufhören müssen.
Also wir können nicht
alle Mutter-Kindpaare mehr aufzählen
Das erste Mal, wo wir festgestellt haben,
hier ist eine Mutter gegeben, schneidet dieser Cut alle Alternativen,
vaiablen Bindungen für Elternteil und weiblich weg.
Und damit kriegen wir immer nur das erste Kind, Mutterpaar,
kein weiteres, also Gott, hier erkriegen wir gar keins.
Hier wollen wir ja nur alle Mütter wissen.
Die Kinder habe ich hier rausgelassen aus Einfachheitsgründen.
Gut, also ja, das ist genau der Punkt.
Wir können es eindeutig machen, aber wir tun mehr.
Wir machen es nicht nur eindeutig, sondern ein eindeutig.
Das ist der Preis, den wir bezahlen müssen,
wenn wir hier in den Suchraum eingreifen
Ja, also auch das ist ein Beispiel für einen Redcut
, weil wir Instanzierungsvarianten verlieren.
Das ist also nicht mehr das originale Prädikat ohne den Cut.
Das Verhalten hat sich verändert. Wenn wir also ursprünglich hier
alle... Ach so, das ist jetzt das Mutterkind-Prädikat.
Sorry, jetzt fahre ich durcheinander. Wenn wir jetzt hier uns
alle Kinder aufzählen lassen wollen, also welche Kinder hat Susi,
dann kriegen wir immer nur die erste auf die Erstinformation geliefert.
Dass Anna auch ein Kind von Susi ist,
hat der Cut weggeschnitten
Deshalb ein roter Cut. Aber in vielen Fällen wollen wir
das natürlich haben. Und einer dieser Fälle ist Default schließen.
Die Fault schließen etwas, was wir täglich immer und immer wieder machen.
Dass wir Standardannahmen tätigen, deshalb der Name Defaults.
Standardannahmen tätigen, die wir aufrechterhalten, solange wie es irgendwie geht.
Und nur in Frage stellen, wenn wir plötzlich mit einer
Ausnahme konfrontiert sind. Also wir gehen selbstverständlich davon aus,
dass Stühle vier Beine haben. Wenn Sie irgendjemanden fragen,
wie viel Beine hat ein Stuhl, wird die Antwort vier sein.
Natürlich gibt es Stühle mit drei Beinen,
mit einem Bein und so weiter.
Möglicherweise auch welche mit sechs oder acht beiden
Aber wir gehen erstmal standardmäßig davon aus, wir haben,
Stühle haben immer vier Beine oder Autos haben vier Räder,
das ist genauso, ja.
Es gibt zahlreiche Ausnahmen, dass Autos andere Zahlen von Rädern haben.
Früher waren drei Radautos sehr populär im Lieferverkehr meistens.
Andere Fälle sind Standardannahmen über das Verhalten von Tieren beispielsweise.
Vögel können fliegen. Ja, das ist selbstverständlich. Damit
arbeiten wir ständig. Trotzdem gibt es zahlreiche Ausnahmen dafür,
wo Vögel nicht fliegen können. Entweder systematisch,
weil sie noch nie fliegen konnten oder aber,
weil sie die Flugfähigkeit aus irgendeinem Grunde verloren haben
Weil der Bauer in die Flügel gestutzt hat oder irgendetwas.
Hier ein Beispiel für die Beinanzahl im Tierreich,
weil das immer oftmals benutzt wird,
um sich verächtlich über die Biologie zu äußern.
Das sind die, die die Käferbeine zählen.
Deshalb die Standardannahme, Tiere haben sechs Beine.
Insekten haben es. Insekten haben sechs Beine. Und Insekten
ist so von allem, was wir so üblicherweise greifbar haben,
das artenreichste Teilgebiet
Da stinken die Säugetiere dagegen ab. Insekten gibt es massenweise
. Deshalb also einfach als Standardannahme das,
was uns am häufigsten begegnen wird im Alltag,
ist ein Insekt und das hat sechs Beine.
Das nehmen wir als den Normalfall an. Und jetzt können
wir eben Ausnahmen dazu formulieren, dass wir sagen,
Vögel haben zwei Beine, Spinnen haben acht Beine,
Lurche haben vier Beine, Säugetiere haben vier Beine.
Wobei wir auch da auch schon wieder sehen,
dass wir Ausnahmen zur Ausnahmen haben,
denn auch der Mensch ist ein Säugetier und hat
nicht vier Beine
Wenn wir das so verwenden, dann kriegen wir natürlich unsinniges Verhalten raus.
Wenn wir jetzt fragen, wie viele Beine hat die Spinne,
da kriegen wir richtig acht heraus. Aber natürlich kriegen wir
über die letzte Klausel dann wieder noch den Standardfall mit hinzu.
Das heißt, wir können uns nicht entscheiden,
hat das die Spinnen und acht oder sechs Beine.
Wir können auch fragen, gibt es Tiere mit vier Beinen
und dann kriegen wir richtigerweise sowohl die Leuche als auch die Säugetiere heraus.
Jetzt wollen wir Default schließen betreiben, dass wir also im
ersten Fall tatsächlich nur die Antwort bekommen, dass es acht Beine sind.
Das heißt, wir versehen das Ganze jetzt mit Cuts dahinter.
Wenn er Klauselkopf gefunden hat, der erfolgreich unifiziert,
dann schneiden wir durch den Cut alle Alternativen weg. Das heißt also,
wenn wir bei der Spinne einmal erfolgreich die Acht als Wertebindung erzeugt haben,
dann werden wir nicht in der Lage sein, weiterzusuchen,
denn dieser Cut schneidet alle Alternativen in der Klausel,
der findet in der Prädikat,
alle Klauseln in der Prädikatsdefinition für Hardbeine weg.
Die gibt es dann nicht mehr. Und dann kriegen wir tatsächlich das Verhalten,
ja, die Spinne hat acht Beine,
aber wir kriegen eben auch wieder das gleiche Problem,
dass wir das bidirektional haben, die Alternativen in der anderen Instanzierungsvariante verschwinden wir auch.
Wir hatten ja ursprünglich die Loche und die Säugetiere sahen.
Das sollte bitte schön klein geschrieben werden. Das sind keine Valiablen,
das ist ein Fehler. Habe ich auch bisher noch nie
gesehen. Das Zweitergebnis Säugetier ist aus der Ergebnisliste verschwunden.
Wir kriegen auch da nur noch ein Ergebnis heraus.
Was auch wichtig ist, ist, dass die Klauselreihenfolge signifikant ist.
Nun hatte ich ja irgendwann schon mal erwähnt,
dass es da ein Prädikat gibt, was nichts anderes macht,
als einen solchen Cut zu verstecken und zumindest eine der Verwendungsweisen
Eine der Verwendungsweisen für den Cut abbildet,
das wollte ich jetzt noch eine Folie machen, halb vergessen.
Das gibt es nicht erst seit kurzer Zeit im SWI-Prolog,
das ist das Once.
Once nimmt ein Ziel, das muss gegeben sein.
Und sorgt dafür, dass dieses Ziel abgearbeitet wird,
aber nach dem ersten Ergebnis kein Backtacking mehr passiert.
Und die Definition von Once ist nichts anderes als Dieses Jahr.
Gib mir ein Ziel, ich rufe es auf,
wenn es erfolgreich war, schneide ich den Suchraum ab und bin fertig.
Das heißt, das wird einmal abgearbeitet.
Dieser Cut verhindert das hier nach Alternativen, gesucht wird.
Alles, was links vom Cut steht, wird eindeutig festgelegt.
Und damit ist er nach dem ersten Ergebnis zufrieden.
Wie könnten wir jetzt dieses Programm,
was wir da oben haben,
mit diesen vielen Cuts drin,
als Mithilfe dieses One
zu schreiben
Ja. Bestimmt hat ein anderer auch noch eine Idee.
Ja, was wäre das Zufall ist oder was passiert ist
, wir kriegen immer nur eine Gesetzesfälle.
Das heißt, wir gehen, dass wir uns einfach rausmachen
Genau, wir müssten ein Prädikat außen drum rum schreiben.
Dort steht der Aufruf von Hat Beine in diesen Ones
drin und dann tut er das genau einmal.
Also irgendwo muss entweder am Prompt oder als Teilziel,
müsste Ones, hat Beine. Ja gut, und dann,
was immer wir wollen an der Stelle, also XY.
Das wäre gleichbedeutend damit. Dann geht er eben die Alternativen von
hat Beine so lange in der Reihenfolge der Datenbank-Eintragungen durch,
bis er das erste passende gefunden hat,
mit dem ersten passenden wird ein Ergebnis erzeugt,
und das One sorgt dafür,
dass nie nach einem zweiten Ergebnis gefragt wird.
Und intern sieht das Ones eben genau so aus.
Mir ist da nicht dabei. Das ist übrigens,
ich glaube, das hatte ich schon mal erklärt,
das ist die sogenannte Metacall Facility, das ist nicht ganz das,
was in der reinen Welt dort stehen müsste.
Das ist Äquivalent, das ist eine Kurzschreibweise für die
ausführlichere Variante,
die heißt Call
Ja, also, Rufe X als Teilziel auf und schneidet danach den Suchraum ab.
Das ist nur eine Kurznotation für das, das ist semantisch äquivalent.
Gut, Default schließen ist ein Spezialfall von nicht-monotonem Schließen,
Monotonie ist hier in Analogie zur Definition einer monotonen wachsenden oder
steigenden oder erwachsenen oder fallenden Funktion in der Mathematik oder in der Analysis,
genauer gesagt,
Wobei es hier eben nicht darum geht,
dass ein Funktionswert größer oder kleiner wird,
wenn der Wertebereich der Argumente größer oder kleiner wird oder
der Wert der Argumente größer oder kleiner wird,
sondern wir betrachten immer eine Menge von Aktionen.
Das sind unsere Logikprogramme, die klauseln in unserem Logikprogramm.
Und die Anzahl der Theoreme, die ich daraus ableiten kann,
das sind also die Anfrageergebnisse,
die ich bekomme
Wenn ich jetzt die Theorie vergrößere,
gilt ohne den Cut immer, dass ich mit mehr Theorie,
mit mehr Axiomen auch mehr Theorie, Theorieme bekomme,
aber auf jeden Fall nicht weniger.
Also es gehen durch Hinzunahme von Axiomen,
gehen keine Theorie mehr, keine Theorie mehr verloren.
Das heißt, Monotonie in der Logik. Ich füge eine
neue Klausel hinzu und dadurch geht mir keines der alten Ergebnisse
verloren, bestenfalls kommen neue hinzu.
Das heißt Monotonie hier. Also, wir haben Wir haben
zwei Aktionenmengen. Das eine ist eine Teilmenge des anderen.
Das ist die kleinere, das ist die größere.
Aus der kleineren kann ich die Menge der Theorie mit T1 ableiten.
Aus der größeren kann ich die Menge der Theorie mit T2 ableiten.
Dann gilt auch, dass die Menge der Theoreme,
die ich aus A1 ableiten kann, eine Teilmenge der Theoreme von A2,
die ich aus A2 ableiten kann.
Es kann ja auch sein, dass sie gleich bleibt,
dass nicht mehr hinzukommen. Aber es dürfen auf keinen Fall
weniger werden. Oder mit anderen Worten,
ich erweitere eine Theorie und das geht mir
keine Schlussfolgerung verloren
Das wird bei der Nicht-Monotonie in Frage gestellt.
Ich nehme neue Aktionen hinzu und dadurch gehen alte Schlussfolgerungen,
die ich mal ableiten konnte, jetzt plötzlich nicht mehr abzuleiten.
Die gehen verloren. Und das ist genau das, was
hier passiert. Also wenn ich klassischerweise habe, das Logikprädikat,
was nur aus der ersten Klausel da oben besteht.
Also alles hat sechs Beine. Das ist ja unsere Standardannahme.
Und ich dann frage, wie viele Beine hat ein Regenwurm?
Dann kriege ich eben sechs heraus
Jetzt erweitere ich das Aktionensystem dadurch durch den Fakt,
dass ein Regenwurm null Beine hat. Und wenn ich jetzt frage,
ich habe meine Theorie erweitert, ich habe ein neues Axiom hinzugenommen,
dann kriege ich auch ein neues Theorium heraus.
Was mir sagt, wenn ich nach der Beinzahl des Regenwurms frage,
kriege ich das alte, das neue Ergebnis,
null Beine heraus, aber das alte ist nicht verloren gegangen.
Das ist immer noch da. Wenn ich das mit dem Cut mache,
das heißt, ich mache jetzt wieder diese Ausnahme hier darum,
dann verschwindet mir das alte Ergebnis.
Wenn ich jetzt frage, wie viel Beine hat der Regenwurm,
ist das alte Ergebnis sechs nicht mehr verfügbar
Durch die Hinzunahme dieser ersten Klausel hier,
die zweite ist ja immer noch die, die da oben steht.
Ich habe das alte Axiomsystem durch eine neue Klausel erweibert und
dadurch geht mir ein Ergebnis, was ich vorher ableiten konnte,
geht mir verloren, das kann ich jetzt nicht mehr ableiten.
Das ist nicht Monotonie. Dass jetzt die Menge der Schlussfolgerung,
die ich aus einer, aus einem gegebenen Logikprogramm ziehen kann, plötzlich kleiner wird.
Oder andersherum, das alte Schlussfolgerung nicht mehr zur Verfügung stehen.
Und die Grundlage ist der Cut dafür. Wir gucken uns bei den Steuerkonstrukten dann noch an,
weitere Verwendungsweisen des Cuts, das bis dahin soll erstmal ausreichend sein,
Ich hatte schon gesagt, dass extra logische Prädikate,
auch die Datenbankmanipulation umfassen, das hat mir auf dem allerersten Übungszettel mal drauf,
dass man beispielsweise Eintragung in der Datenbank vornehmen kann, dass man Eintragung in der Datenbank wieder beseitigen kann.
Das gibt es in verschiedenen Varianten,
wir haben nur eine Teilmenge davon uns angeguckt,
also beispielsweise kann ich hier nicht nur eins,
das entfernt eine einzelne Klausel, und zwar die erste,
wo dieser Kopf erfolgreich ist, beseitigt alle,
die auf den Thermen hier unifizieren, wo der Kopf der Klausel auf diesen Thermen unifiziert.
Auch das Consult ist ein Seiche, gehört mit dazu,
was wir ja standardmäßig bei jedem Testlauf am Anfang durchführen.
Und was auch dazu gehört, ist, dass wir
die Suche in der Datenbank der Klauseln wieder in die eigene Hand nehmen können.
Also das, was die Prolog-Suche standardmäßig für uns bisher getan hat,
können wir auch aktiv anstoßen, indem wir sagen,
suche mir die Klauseln Oder nein, gib mir die Körper aller Klausel,
wo der Kopf auf das erste Argument unifiziert.
Ja, wir geben hier einen Therm an,
der mit den Köpfen aller Klauseln unifiziert wird und wann immer dies erfolgreich ist,
kriegen wir den Körper dieser Klausel, instanziiert mit den vajablen Bindungen, die hierbei erzeugt worden sind.
Das ist genau die Grundfunktionalität, die die Suche ständig durchführt.
Ja, unifiziere den Kopf der Klausel,
instanziiere die Vagablen im Körper der Klausel und rufe das Ganze wieder als Teilziel auf.
Hier wird es nicht als Teilziel aufgerufen,
hier wird es uns zur Verfügung gestellt und wir können entscheiden,
was wir mit dem Körper der Klausel machen wollen
Ob wir es so abarbeiten wollen, wie das Produktsysteme
standardmäßig tut oder ob wir ganz andere Pläne haben damit.
Auch das kommt im Abschnitt Meta-Programmierung. Wichtig ist nur,
dass hier unifiziert wird. Das heißt, hier werden Vayablen
instanziert. Und wenn sie hier instanziert werden,
dann werden sie auch im Körper der Klausel instanziert.
Wenn wir also uninstanzierte Klauseln haben wollen, dürfen wir nicht
mit einem instanzierten Ziel aufrufen. Und das wird der Grund sein,
weshalb wir frische Prädikatsausdrücke erzeugen müssen, die nur uninstanzierte Variablen enthalten.
Das ist das, was sie vorhin angedeutet habe,
dass das eine Funktionalität ist, die wir tatsächlich mal brauchen werden.
Wir wollen einen Ausdruck haben, der nur einen Funk da
hat und eine Stelligkeit hat. Alles andere muss uninstanziert sein,
damit wir nur die Klausel bekommen, aber nichts hier drin unifizieren.
Denn das würde bereits Teile der Verarbeitung vorwegnehmen. Wir wollen
ja die Unifikation in dem System aus der Hand nehmen.
Wir wollen es selbst organisieren. Andere Varianten sind dann,
dass man die so und sovielte Klausel für eine Prädikatsdefinition
sich geben lassen kann und so weiter.
Also da gibt es zahlreiche Möglichkeiten, auch in die Arbeitsweise des Systems wieder einzugreifen.
Globale Variable war auf dem letzten Übungszettel dran,
das ist das Flag-Prädikat, das Flag-Prädikat, hat immer hier einen Namen,
das ist der Name der globalen Variablen, und ist als Update-Operation implementiert.
Ich habe immer den alten Wert und ich habe den neuen Wert.
Und ich kann dann eben einen Aufruf machen,
indem ich sage, gib mir den alten Wert und erhöhe
ihn um eins
Ja, und wenn denn im ersten Falls das uninstanziert ist,
initialisiert er das mit null und jeder weitere Aufruf des
gleichen Ausdruckes hier erhöht mir die Variable um eins.
Das heißt aber, das Ganze ist jetzt zustandsabhängig.
Ich verändere hier den Zustand meines Prologsystems. Während anfangs der
Wert von Fleck null war, ist er dann eins,
dann ist er zwei und so weiter.
Und das, was wir bisher immer hatten,
wann immer ich ein Prädikat zweimal aufrufe, exakt den gleichen Aufruf,
haben wir wegen der Nebenwirkungsfreiheit immer das gleiche Ergebnis bekommen.
Das ist deklarative Programmierung. Jetzt verlassen wir den Bereich der
deklarativen Programmierung. Hier machen wir
zustandsorientierte Programmierung
Das geht in einen Polog, genauso wie es auch in Steam geht.
Sie erinnern sich da, sind es die Bang-Funktion,
Set Bang und dergleichen mehr. Aber wir müssen wissen,
was wir tun. Wir sollten es nur dann anwenden,
wenn wir tatsächlich wissen, dass es anders nicht geht.
Und das ist genau in der Übung mit dem Rangieren der Fall gewesen.
Ich suche die Lösung, die mit dem wenigsten Rangieraufwand verbunden ist.
Und ich könnte mir natürlich alle Rangiermöglichkeiten auflisten lassen,
Allerdings ist der Aufwand, wer es gemacht hat, hat es mitgekriegt.
Der Aufwand ist nur die erste überhaupt anzeigen zu lassen, teilweise schon gigantisch hoch.
Je nachdem, wie geschickt man sich da angestellt hat.
Sie alle aufzählen zu lassen, ist praktisch unmöglich.
Jetzt möchte ich so früh, wie es geht, den Suchraum beschneiden.
Und das kann ich auf die Art und Weise machen,
indem ich also eine globale Variable setze. Die erste Lösung,
die ich bekomme, setzt eine Grenze und alle Lösungen,
die schlechter sind als die, die ich sehr bereits gesehen habe,
betrachte ich überhaupt nicht weiter.
Die schneide ich einfach raus. Und auf die Art
und Weise wird das ganze Problem überhaupt erst fraktabel.
Das ist ein Verfahren, was üblicherweise als Branch and Bound bezeichnet wird.
Ich habe eine, ich arbeite den Suchraum ab, alle Varianten,
aber ich setze mir eine Schranke, da sich bestimmte Komplexität nicht überschreite.
Und das ist in diesem Falle die Komplexität der Weichenstraße,
die ich bisher habe stellen müssen.
Auf die Art und Weise kann man also das Suchproblem beschränken.
Man arbeitet nicht mehr der gesamten Suchraum ab,
sondern dynamisch in Abhängigkeit davon, wie gut sind die Ergebnisse,
die ich bisher schon gesehen habe, entscheidet man,
ob man dort weitermachen will oder nicht.
Und An dieser Stelle brauche ich es tatsächlich,
wenn ich, ich will ja in die Suche eingreifen,
das ist ja genau mein Ziel.
Ich möchte nicht, dass der gesamte Suchraum durchmustert wird,
weil das nicht effizient machbar ist.
Deshalb möchte ich in die Suche des Prolog-Systems eingreifen und
ich muss zu diesem Zweck verschiedene Ableitungen miteinander vergleichen.
Ich hatte bis hier eine Ableitung,
die ist mit fünf Schritten ausgekommen und jetzt habe ich eine,
die ist auch fünf lang, aber ich bin noch nicht am Ziel.
Das heißt, ich bräuchte einen sechsten Schritt und in dem Augenblick zeichne,
dann danke, brauchst du gar nicht weiter zu gucken.
Ich kenne ja schon eine bessere. Ich möchte das,
dass das System nicht seine Suche durchführt,
sondern ich möchte entscheiden,
ob weiter gesucht wird oder nicht
Und dann brauche ich eine globale Variable, weil ich zwei Ableitungen miteinander vergleichen muss.
Und es gibt nur wenige Möglichkeiten im Prolog, unterschiedliche Ableitungen miteinander zu vergleichen.
Das Find All, was wir bisher benutzt hatten,
wäre so etwas. Ich sammle alle Lösungen und entscheide dann.
Aber das scheitert eben genau aus, weil es zu aufwendig ist.
Gut, ja, damit kann ich anfangen,
in Analogie zur funktionalen Programmierung, wo sie ja Funktionen,
höherer Ordnung kennengelernt haben. Ich hoffe,
dass Frau Röschler auch schon soweit ist, ist sie,
okay, habe ich hier den Begriff der Prädikate höherer Ordnung geprägt,
Prädikate höherer Ordnung sind also all solche die,
wie auch in der funktionalen Programmierung,
entweder Prädikatsaufrufe meistens als Argumente entgegennehmen, möglicherweise auch,
nee, das geht schlecht.
Oder aber die Prädikate erzeugen und dann abarbeiten.
Also dies hier ist praktisch ein Metaprädikat, was wir haben.
Das wird auch das allererste sein, was da steht.
Prädikatsaufruf steht als erstes in der Liste drin,
nimm ein Ziel und führe es aus.
Das tut das Call
Was in der funktionalen Programmierung sehr, sehr schön geht,
dass sich also eine Funktionsdefinition als Wert einer Funktion
erzeugen kann und diesen Wert sofort in die Abarbeitung übergebe.
Das geht in der Logikprogrammierung nicht so gut. Da hat
die Logikprogrammierung echt eine Achillesferse. Warum geht das nicht so gut?
Warum kann ich nicht einfach dynamisch ein Prädikat erzeugen und sagen,
dann arbeite es doch mal gleich ab.
Ich habe mal eine Idee, wo ist die Hürde?
Ja, also wir postfizieren ja eigentlich nur die Arbeit
der Milchsmittel
Eigentlich auch zu tief. Also man könnte einfach machen,
dass es dann bereit ist, dieses Vedikat weg,
aber selbst wenn, uns wir beide irgendwo eine Funkwahl,
Vetelefunkwahl, da war Gabel uns setzen können.
Ja, aber ich muss doch,
müsst ihr doch sowas analog hier zumachen können.
Hier muss es ein Aufruf sein. Ja, ich kriege
einen Aufruf für ein Prädikat und ich schiebe diesen Aufruf in die Suche.
Warum kann ich nicht ein Prädikat definieren,
was eine Prädikatsdefinition entgegennimmt? Und diese Prädikatsdefinition sofort abarbeitet.
Das muss doch
irgendwie gehen
Der Unterschied ist, dass ich sie in Prolog erstmal in
die Datenbank eintragen muss und dann kann ich sie abarbeiten.
Weil die Abarbeitung immer nur die Suche in der Datenbank ist,
das ist das wesentliche Ding. Nur Dinge, die Prädikatsdefinition,
die in der Datenbank stehen, kann ich tatsächlich abarbeiten,
weil Aktivieren eines Teilziels immer heißt, durchsuche die Datenbank.
Anders geht es nicht. Und ich müsste ein völlig separaten Mechanismus etablieren,
um dieses Verarbeiten von ad hoc, Definition zu ermöglichen.
Und das ist nicht so ohne weiteres möglich. Ich kann
es natürlich immer simulieren. Ich kann ein Prädikat schreiben,
das nimmt eine Prädikatsdefinition als Liste von Klauseln,
beispielsweise entgegen, macht nichts anderes,
als dass es diese Klauseln in die Datenbank einträgt und anschließend aufruft.
Und wenn gewünscht, hinterher wieder rauslöscht aus der Datenbank,
das kann ich natürlich machen, aber das ist ein Hack.
Das ist nicht die Eleganz, die ich in der funktionalen Programmierung habe,
dass ich das Ding on the fly erzeuge,
nicht mal den Namen dafür vergebe,
dass ich einfach nur einen Lamderausdruck erzeuge und sofort in das Map reinschieße.
Das ist doch Also ich finde es immer wieder bewundernswert.
Das geht hier nicht so schön einfach. Gut, wir werden sehen,
dass alle Steuerstrukturen, die wir zusätzlich noch hinzufügen können,
wir haben ja als Steuerstruktur schon immer unsere rekursive Suche.
Wir können natürlich auch klassische Steuerstrukturen wie If,
wenn If and Ells, Four und Wile und dergleichen hier auch definieren.
All das werden Prädikate höherer Ordnung sein. Das,
was wir bereits kennengelernt haben, das Find All,
wo ich Ergebnisse aggregiere, ist ein Prädikat höherer Ordnung.
Denn es nimmt einen Aufruf entgegen,
ein Ziel entgegen und dieses Ziel
wird abgearbeitet
Und die Krönung des Ganzen ist dann die Metaprogrammierung,
wo wir anfangen, die Semantik des Systems zu ändern.
So, also Prädikate, höherer Ordnung,
erlauben Prädikataufrufe als Argumentbelegung und können die in die Abarbeitung überführen.
Beispiele sind eben das, was wir hier auch schon hatten.
Das, was nur noch neu hinzukommt, sind die Konsistenzforderungen.
Sie können auch Prädikatsdefinitionen erzeugen, müssen die dann aber in die Datenbasis eintragen über ein Assert.
Und das Problem ist, dass das immer Prädikatsdefinitionen,
der hat nicht in einem Ausdruck erfolgen, sondern klauselweise erfolgen.
Das heißt, ich müsste irgendeinen Mechanismus erfinden,
dass ich die nicht nur eine Klausel,
sondern eine ganze Sequenz von Klauseln in die Datenbank eintragen kann.
Der einfachste Fall, das ist genau das, was wir hier schon hatten,
das Call nimmt ein Ziel entgegen und ruft dies als neue Zielvorgabe für die Suche auf.
Das Ganze ist eben insbesondere sinnvoll mit dynamisch erzeugten Prädikatsaufrufen,
wo ich den Den Thermen, den ich dort aktivieren will,
vorher gar nicht statisch habe, sondern erst im Verlauf der Abarbeitung erzeugen kann.
Also wir hatten das beispielsweise im einfachen Testprogramm. Wir haben,
wir schreiben uns unsere Testbeispiele in die Datenbank und rufen über das Call genau diese Testbeispiele auf,
um zu gucken, ob sie das gewünschte Ergebnis produzieren.
Es ist auch sinnvoll, wenn ich ein Ziel verzögert aktivieren will.
Also ich nehme eine Klausel, der im System aus der Hand,
und das ist die Metaprogrammierung, entscheide dann,
was ich mit den Teilzielen in der Klausel mache,
Und erst, wenn ich das erledigt habe,
das kann zum Beispiel Trace-Informationen sein.
Ich möchte Dress-Informationen auf der Konsole ausgeben.
Danach möchte ich aber die Abarbeitung wie gehabt weiterführen.
Und dann muss ich natürlich das alte Teilziel wieder aktivieren.
Oder aber auch ein modifiziertes Teilziel. Wenn ich Programmoptimierung betreibe,
dann ändere ich die Definition erst und anschließend wird das Ziel aufgerufen.
Und dazu brauche ich dieses Call. Es gibt eben diese Meta-Call-Facility,
das ist genau das, was ich hier schon angedeutet habe.
Eine Struktur, also eine Vagable einfach im Körper einer Klausel
heißt, was immer der Wert dieser Vagablen ist,
wird als Ziel aufgerufen als Teilziel
Und dann kann ich eben dieses My Call mit einem Prädikatsaufruf intern hier aufrufen.
Und das macht nichts anderes als dieses Ding hier innen drin
in die Suche zu schicken und das Ergebnis entsprechend auszugeben.
Das ist die erste, das erste einfache Meta-Programm, was
wir geschrieben haben. Jetzt arbeitet nicht mehr das Prologsystem,
sondern es arbeitet My Call. Nur ist die Semantik von
My Call trivial. Denn sie macht nichts anderes,
als dass sie das, was ich ihm gegeben habe,
aufruft und sagt, du Prolog-System,
übernimm mal,
ich kann es nicht
Das ist alles, was My Call tut. Das ist ziemlich billig.
Aber nichtsdestotrotz, das ist der Einstieg in die Metaprogrammierung.
Dieses Schema werden wir erweitern, um zusätzliche Funktionalität.
Dass eben tatsächlich jetzt unser Programm die Kontrolle übernimmt und nicht sofort sagt,
ach, lass es mal lieber das Prologsystem machen.
Gut, ja, in der Definition der Dissuption zum Beispiel
wird sowas ganz, ganz oft verwendet.
Und da sehen wir sehr, sehr schön, dass Dissuption eigentlich
nur so ein taktischer Zucker ist für unsere Normalaufnahme in die Klausel-Liste.
Also wir haben definieren eine eine Disjunktion von X und Y
als das Abarbeiten des Teil CLS X oder alternativ als das Abarbeiten des Teilziels Y.
Das genau ist die interne Definition der Disjunktion. Das ist
keine Magie. Und an der Stelle sehen wir auch,
es ist vollständig ersetzbar. Denn das, was diese Definition hier macht,
das können wir auch mit der Hand machen,
das wäre also zweites Jungte auf unterschiedliche Klauseln aufteilen.
Das ist immer der Trick, wie man das Junktion vermeiden
kann, wenn die Ausdrücke zu kompliziert werden
Gut, also an der Stelle sehen wir diese Meta-Call-Facility,
diese Variable im Körper der Klausel heißt nichts anderes als Rufe,
Teilziel, er rufe den Wert von X als Teilziel auf.
Und hier rufe den Wert von Y als Teilziel im Körper der Klausel auf.
Im Kopf der Klausel sehen wir hier die Operator-Notationen.
Also wir können Therme, wenn sie als Operator definiert sind,
ohne weiteres auch im Kopf der Klausel als Operator aufschreiben.
Wir könnten genauso gut Semikolon, Klammer auf,
Groß X, Kommer groß,
Y,
Klammer zuschreiben
Völlig äquivalent. Die Operator-Notation ist ja nur eine andere syntaktische Notation für die Funkto-Argumentschreibweise.
Ach, da habe ich es ja schon drin.
Das wollte ich dann, habe ich es letztes Jahr schon gemacht.
Sorry, das habe ich nicht mehr angeguckt. Okay,
aber die Zeit ist oben hin abgelaufen. Das,
was hier draufsteht, das hat mir schon an der Tafel
die einfachste Verwendungsweise des Cuts ist,
eben diese Ones-Definition.
Auf die Art und Weise, dass so viel erlauben sie mir noch,
auf die Art und Weise kann ich das Member Check jetzt definieren,
Member Check unterscheidet sich von Member,
das ist nach dem ersten erfolgreichen Pfund,
die Verarbeitung abbricht und genau das kann ich durch das Ones hervorrufen.
Und damit möchte ich heute auch schließen,
schönen Dank für die Aufmerksamkeit und bis nächste Woche