Woche uns mit Listenprogrammierung befasst und hatten uns Anwendungen angeguckt.
Und ein Anwendungsbeispiel war einen endlichen Automat zu programmieren,
der besteht aus Zuständen und Übergängen zwischen den Zuständen und den Symbolen,
die beim Traversieren einer Verbindung zwischen zwei Zuständen emittiert werden.
Die Idee besteht dann darin, dass man,
während man diesen endlichen Automaten durchwandert, jeweils die,
mit den Kanten verbundenen Symbole in einer Liste aufsammelt und damit sind wir beim Thema.
Und zwar passiert es hier an dieser Stelle.
Ich mache also einen Übergang von einem Startzustand zu einem Zielzustand.
Ich nehme eine Transition von dem Startzustand zu einem Zwischenzustand und
was immer dort an Symbolen an dieser Transition dranhängt,
wird in die Liste aufgenommen und das wird aufgesammelt und als Ergebnis nach draußen übergeben an dieser Stelle.
Hier die aufgesammelte Liste, die nach draußen als das generierte
oder von dem Automaten akzeptierte Wort zur Verfügung gestellt wird.
Wir haben die Idee weiter benutzt. Ach so,
wir brauchen dieses Beispiel noch. Der Automat,
den ich angegeben hatte, der war so spezifiziert,
dass er einen Zyklus erzeugt und zwar immer dadurch,
dass ich hier an dieser Stelle vom Zustand S1 in den Zustand S1 übergehen kann,
indem ich Ys emittiere.
Und da ich als allererstes vom Startzustand S0 in den Zustand S1 komme,
wird die weitere Möglichkeit dann sein,
entweder in den Endzustand zu gehen oder aber alternativ
diesen Zyklus zu benutzen
Und dann von S1 wieder in den Endzustand zu gehen. Man wird nie,
weil die ist hier bereits unendlich viele Möglichkeiten gibt,
Wörter zu erzeugen, wird man nie in die Verlegenheit kommen,
die Transition nach S2 zu benutzen und von S2 ausgehend dann Z zu generieren.
Das liegt an der eingebauten Suchreihenfolge von Prolog, nämlich Tiefe zuerst suche,
die ein Ergebnis nimmt und mit diesem Ergebnis weiterarbeitet.
Und eines der Ziele, was als nächstes kommt,
wird es sein,
diese Suchreihenfolge zu modifizieren
Wir sehen das in dem Ergebnis oder wir haben es schon,
in dem Ergebnis gesehen, wenn wir jetzt also sagen,
für diesen Automaten generiere mir alle Wörter, dann sehen wir,
dass wir nur die Wörter mit dem Y generiert bekommen,
die Wörter mit dem Z sind in der Ergebnismenge nicht enthalten.
Wir haben die Idee des Aufsammelns von Zuständen oder von Orten,
in denen ich bereits mal war, dann benutzt,
um Suche im Labyrinth, also Navigationsprobleme,
Fahrplattenprobleme oder dergleichen zu spezifizieren und auch dort müssen wir die,
wenn wir nichts tun,
können wir natürlich munter im Kreise wandern und die Idee ist,
dass wir die zurückgelegte Wegstrecke auf einem Argument sammeln und dort immer überprüfen,
ob wir an diesem Ort schon mal waren oder nicht.
Im Unterschied zu dem endlichen Automaten müssen wir dieses Sammeln der Orte
jetzt bereits machen, ehe wir den rekursiven Abstieg machen.
Denn wir wollen im rekursiven Abstieg überprüfen, wo waren wir schon überall.
Das heißt, hier ist also Endrekursion angesagt,
danach nutzt uns das Sammeln der Liste nichts mehr.
Und dazu brauchen wir ein zusätzliches Argument,
ein Akkumulator, auf dem wir die Liste sammeln.
Das ist hier die dritte Argumentstelle, die wir verwenden. Und wir gucken immer nur nach,
ob denn hier in der bereits zurückgelegten Wegstrecke dass der neu zu besuchende Ort,
in diesem Falle also B, es gibt eine Verbindung von A nach B,
dann schauen wir nach, ob der neu zu besuchende Ort B nicht bereits auf der zurückgelegten Wegstrecke enthalten ist.
Und nur wenn er das nicht ist,
dann machen wir den nächsten Rekursionsschritt, fügen aber das B,
was wir gerade besucht haben, zu der Liste der bereits besuchten Orte zu,
dazu, sodass also hier die Liste der Orte von hinten nach vorn aufgebaut wird.
Sie startet mit dem Staatszustand hier und im Endzustand,
wenn ich den Ziel, wenn ich das Ziel erreicht habe,
enthält sie die besuchten Orte in umgekehrter Reihenfolge
Wir sehen das hier, dass jetzt tatsächlich der Zyklus verschwunden ist.
Natürlich kriegen wir doch mehrfach Ergebnisse,
weil wir auf verschiedene Arten und Weisen ein Ziel erreichen können,
aber wir fahren nicht die ganze Zeit im Kreise herum.
Das ist der wesentliche Fortschritt, den wir erreicht haben.
Wir wollen uns möglicherweise natürlich auch noch die Strecke ausgeben,
die wir hätten nehmen müssen mit unserem Prädikat.
Und dazu können wir wieder ein weiteres Argument zur Verfügung stellen,
das dann im Rekursionsabschluss, die zurückgelegte Wegstrecke einfach nochmal umdreht,
weil wir ja das in Umgebung kehrte Reihenfolge aufgesammelt haben,
hier einfach nochmal in der Reihenfolge umdreht und dann in der richtigen Reihenfolge so,
wie die Orte nacheinander besucht werden, nach außen zur Verfügung stellt.
Ansonsten ist die Struktur des Programmes genau das Gleiche. Alles,
was wir eben brauchen, ist die vierte Argumentstelle,
die für die Übergabe der das umgedrehten Wegstreckenlänge,
des Weges nach außen zur Verfügung stellten.
Und damit sind wir tatsächlich in der Lage, Navigationsprobleme zu bearbeiten.
Wir können jetzt also in diesem Labyrinth, was wir hier haben,
fragen, wie komme ich nach D, nein,
wie komme ich,
wo kann ich von D
überall hinfahren
Das ist ja der Startort,
den Zielort haben wir unterspezifiziert und wir wollen natürlich die Wegstrecke sehen,
die wir zurücklegen müssen und dann ist eben die Antwort,
ja, wir können von D nach A kommen,
indem wir einfach von D nach A fahren.
Wir können auch B kommen,
indem wir nach OP fahren, das sind die Trivialfälle,
aber wir können eben auch von D nach B kommen,
indem wir erst nach A fahren und danach B oder wir kommen von D nach C,
indem wir D, A, B,
C der Reihe nach besuchen und so weiter und so fort.
Und wir sehen wieder, es sind endlich viele Ergebnisse,
wir fahren also dank der Zyklenüberwachung durch diese Notemember-Prädikate hier
nicht immer vor dem Kreise herum
Ich hatte es schon in der Übung auch deutlich gemacht,
dieses Nordmember, hier ist Overkill, weil es backdrickbar ist.
Und wir wissen in diesem Falle, erstens,
wir wollen nicht wissen, ob ein Ort zweimal drauf ist und zweitens,
das Programm ist so angelegt, dass es nicht passieren kann,
dass ein Ort zweimal besucht wird.
Dafür ist eben genau dieser Test da. Das heißt,
hier können wir auch ein reduziertes Intermember verwenden,
dass es im SVI-Prolog unter Member Check, also C-H-H-S-C-H-C-K,
an Member hinten dran gefügt, steht zur Verfügung.
Das ist ein Member, was nach dem ersten Ergebnis,
was es nach dem ersten positiven Ergebnis, was es gefunden hat,
die Suche abbricht und mehr wollen wir ja auch nicht.
Wir wollen ja nur feststellen, ist es da in
der Liste,
ja oder nein
Das Backtracking ist nur unnötiger Suchaufwand, denn wir wissen ein
zweites Mal, kann es in der Läste nicht auftauchen.
Gut, dritter Punkt ist jetzt, das, was Sie bereits angekündigt hatte,
das Prolog-System ist eben standardmäßig auf tiefe Suche getrimmt.
Das ist in einigen Fällen nicht erwünscht. Zum Beispiel in
unserem endlichen Automaten sehen wir nur die Hälfte des Suchraumes,
die andere Hälfte des Suchraumes kriegen wir nie zu Gesicht,
weil wir uns bereits in der ersten Hälfte des Suchraumes in der Tiefe verlieren.
In anderen Fällen kann es durchaus sein, dass sich die Tiefensuche nicht attraktiv ist. Immer dann,
wenn ich mir per alternative Lösungen miteinander vergleichen will,
dann brauche ich verschiedene Ableitungspfade in dem Logikprogramm parallel zur Verfügung und dann ist die tiefe Suche ungeeignet,
weil sie erst eine Lösung bis ganz zum Ende abarbeitet und die alternativen Lösungen erst hinterher behandelt.
Auf der anderen Seite ist die Tiefensuche, die wir machen,
aber auch wieder sehr attraktiv bezüglich ihrer Speicherplatzbedarfs.
Also zumindest für die meisten praktischen Suchprobleme ist das so,
weil ich eben nicht alle Alternativen und das können exponentiell viele werden,
je nach Verzweigungsfaktor Sondern ich muss nur eine Alternative jeweils abspeichern und
die in die Tiefe und die meisten Suchprobleme sind so,
dass die Tiefe des Suchraumes gegenüber der Breite des Suchraumes vernachlässigbar ist.
Und dann ist natürlich Tiefensuche immer von der Effizienz her zu bevorzugen.
Aber manchmal braucht man die Breitensuche und wir haben ein Beispiel gesehen,
wofür wir die brauchen. Wenn ich das haben will,
und das ist jetzt sozusagen ein Schritt, den wir machen,
ich muss dem Prolog-System die Kontrolle aus der Hand nehmen.
Ich muss Dinge, die das Prolog-System bisher für mich gemacht hat,
in die eigene Regie übernehmen und tatsächlich durchführen.
Und das ist jetzt hier, wir müssen die Suchraumzustände,
die bisher also rein durch ich die tiefe Suche des Prolog-Systems verwaltet worden sind,
die müssen wir explizit repräsentieren und wir müssen entscheiden,
wie wir mit den Zuständen umgehen wollen.
Also was der nächste Zustand in der Suche sein soll,
den wir besichtigen wollen. Entweder wir gehen weiter in
die Tiefe der Suche, warum ist hinein?
Oder wir kümmern uns erstmal um die Alternativen,
die in der Breite zur Verfügung stehen.
Das ist der erste Schritt, wo wir anfangen,
Dienste des Prolog-Systems beschritt außer Kraft zu setzen.
Wir werden also in der Metaprogrammierung noch andere Sachen kennenlernen,
wo wir auch dem System die Unifikation noch aus der Hand
nehmen und dergleichen mehr,
um bestimmte Verarbeitungseffekte zu erzeugen
Wir gehen wieder zu dem endlichen Automaten zurück.
Und wir bauen jetzt die Suchzustände auf auf eine Agenda.
Die Agenda kann entweder ein Kellerspeicher sein, dann ist es eben eine Tiefen-zuerst-Suche.
Oder es kann eine Warteschlange sein, dann wird es eine Breitensuche.
Und wir müssen uns irgendwie einigen, wie die Agenda aussehen soll.
Und einen Suchzustand in einem endlichen Automat ist immer dadurch gekennzeichnet,
dass ich sage, in welchem Zustand befindet sich der Automat gerade?
Und was ist die Strecke, die er bisher zurückgelegt hat?
Und wenn ich das weiß, ist eindeutig charakterisiert,
wie es weitergehen kann. Also ich muss eine solche Datenstruktur
im Verwalten in einer Liste, sodass ich alle Alternativen,
die von der Suche noch berücksichtigt werden müssen,
in der Liste zur Verfügung habe.
Und diese Liste nennt man Agenda. Wir nehmen das
bisher erzeugte Wort wieder rückwärts. Das hatten wir schon bei
dem endlichen Automaten gesehen. Das ist relativ bequem.
Wir haben also die letzten besuchten Zustände immer vorn.
Wir sind hier über das X reingekommen und jetzt haben wir
zweimal das Y besucht und nun sind wir immer noch im Zustand S1 und müssen weitersehen,
dass wir den Zustand S3 erreichen.
Das war ja unser Endzustand. Weil wir das speichern,
nee, das hat damit nichts zu tun. Wir vereinbaren,
dass wir die Agenda immer vom Listen anfangen, bearbeiten.
Also wir schauen immer nach, was zu tun ist.
Das steht am Listenanfang. Und dann wird die Steuerung über das,
was als nächstes zu tun ist, dadurch erreicht,
wo tragen wir Agenda-Elemente ein.
Und wenn wir das am Listen anfangen machen,
dann ist es eben ein Kellerspeicher und wir kriegen Tiefensuche.
Wenn wir das am Listen Ende machen,
dann ist es eine Warteschlange und wir kriegen Breitensuche.
Das ist eigentlich die einzige, das einzige Steuerelement,
was wir brauchen, um dem System mitzuteilen,
wie soll die Suche im Weiteren ablaufen
Gut, also, wir es euch brauchen, ist,
wir müssen irgendwie die Agenda befüllen und dann immer durch die Suche weiterreichen.
Also, wenn wir jetzt unser General-Prädikat wieder hernehmen,
dann sind wir natürlich anfangs im Anfangszustand und wir müssen jetzt in diesem Anfang,
für alle Anfangszustände, A, ja,
die stehen ja mit als A von irgendetwas und die Vagable hier drin sind die Zustände,
die mögliche Anfangszustände des Automaten sind.
All diese Zustände müssen wir aufsammeln. Und das machen wir
natürlich mit einem Find All und dieses Find
All sammelt jetzt genaue Strukturen dieser Art, die sagen,
ich bin in Zustand sowieso und habe noch nichts getan
Das ist nämlich ein Startzustand. Alle Ausdrücke dieser Art werden
aufgesammelt in die Agenda geschrieben und damit geht's los.
Diese Agenda wird jetzt dem eigentlichen Generieren übergeben und weil wir
hier in dieser Agenda-Eintragung die erzeugten Wörter rückwärts abgespeichert haben,
müssen wir zum Schluss das Ganze nochmal umdrehen,
damit weil sie wieder in der richtigen Reihenfolge sehen.
Für unseren Automaten ist das irrelevant, weil die Wörter ohnehin symmetrisch sind.
Da macht das keinen Unterschied. Ich hätte vielleicht ein drittes
Symbol nehmen sollen, dass man das Ende vom Anfang unterscheiden kann.
Okay, das ist aber eine kleine Modifikation nur.
So, und jetzt brauchen wir die Verwaltung dieser Agenda und
wir haben zwei Möglichkeiten
Entweder wir haben hier auf der Agenda,
und zwar interessieren wir uns immer nur für den ersten Eintrag.
Hier hinten können noch weitere Einträge kommen. Wir gucken immer nur
den ersten Eintrag und fragen, was ist zu tun?
Und dann sagt uns, dass die Agenda, ja,
wir sind in diesem Zustand, der hier End genannt ist, verräterischerweise schon.
Und wir haben bis zu diesem Zustand ein bestimmtes Wort erzeugt.
Und wenn wir jetzt überprüfen oder nachweisen können, dass dieser Zustand,
den wir hier schon erreicht haben, ein Endzustand ist,
dann können wir Heureka rufen und wir sind fertig.
Dann geben wir dieses Wort, was sie erzeugt worden ist,
nach draußen aus.
Das wird nochmal umgedreht und steht dann als Ergebnis
zur Verfügung
Wenn das nicht der Fall ist,
wenn es also kein Endzustand ist,
dann sind wir in irgendeinem Zwischenzustand,
haben bis dahin ein bestimmtes Wort erzeugt und wir haben hier eine Restagenda noch.
Die wird jetzt plötzlich interessant. Dann müssen wir schauen,
wie da solche ein Find-All machen.
Gib mir alle Zustände, die eine bestimmte Bedingung erfüllen.
Und diese Bedingung heißt in diesem Fall, die mich von Z wegführen.
Ich bin im Zustand Z. Ich muss aus Zustand Z weg,
irgendwo hin, ist mir egal wohin.
Ich will alle wissen. Und dann kriege ich natürlich
von all diesen Transitionen mitgeteilt,
welches Symbol
da emittiert wird
Und das sammle ich jetzt wieder eben hier auf,
indem ich sage, ich komme von Z,
lande in dem Zustand Z Next,
habe ein Symbol e-emittiert und das schreibe ich vor,
dass bisher schon erzeugte Worte davor.
Das ist der nächste Ausgabe, die produziert wird. Gut,
und diese Listen dieser Art sammle ich jetzt in einer großen Liste,
die habe ich ja New Items genannt,
das sind eben die neu hinzukommenden Suchmöglichkeiten vom Zustand Z
aus und jetzt entscheidet es sich,
was will ich tun
Es ein Stack werden oder soll es eine Agenda werden?
Entweder ich setze die neuen Items vor,
die alte Agenda, das ist nämlich der hintere Teil hier.
Und dann werden sie als nächstes auch wieder als erstes verarbeitet.
Dann habe ich einen Kellerspeicher implementiert und das Ganze resultiert in Tiefensuche.
Oder aber ich schreibe die neuen Items hinter die alte Agenda und kriege eine neue Agenda.
Dann ist das eine Warteschlange und dann müssen die neuen Items warten,
bis ich die alten erstmal abgearbeitet habe.
Und das ist dann eine Breitensuche, die uns genau die Ergebnisse liefert,
die die bisherige Tiefensuche uns vorenthalten hat.
Allerdings mit dem Auf, mit dem Problem, das möglicherweise diese Agenda,
die ich hier immer durchreiche, exponentiell stark wächst.
Darüber muss man sich immer im Klaren sein, wenn ich ein Suchproblem habe,
wo ich in jedem Schritt einen gleichen Verzweigungsfaktor habe,
dann wächst diese Agenda exponentiell an mit der Zeit.
Gut, und ja, wenn ich das getan habe,
dann nehme ich eben die neue Agenda,
die hier erzeugt worden ist,
schicke sie wieder in das Generieren hinein und möchte das Wort zurückbekommen,
was dabei erzeugt worden ist,
das,
was einfach nach oben hier übergeben wird über diese Co-Referenz
an die Aufrufebene
Damit haben wir dann die Regussion wieder und jetzt wird das
Ganze wieder gestartet. Wir nehmen wieder die Agenda her,
fragen nach dem Zustand, in dem wir gerade sind,
das ist Z und gucken,
wie wir aus dem Zustand Z wegkommen und da emittieren,
welcher Symbole und was sind die Folgezustände.
Das schreiben wir wieder hier in diese Listenpattern hinein,
sammeln es in New Items und Rin in die Agenda zur Weiterverarbeitung.
Nimmen wir das Ganze jetzt testen, dann sehen wir im Kellerspeicher,
in der Kellerspeicher-Variante genau das Verhalten, was wir bisher auch hatten.
Wir landen im Zustand S1 und erzeugen dort unendlich viele Ys.
Wir kommen nie dazu, dass wir den Zustand
erst zweimal besichtigen, aber genau das,
Genau das passiert in dem Fall der Warteschlange.
Am Anfang sehen wir es noch nicht. Wir sind also
hier über den Zustand S1 in den Endzustand gekommen.
Hier sind wir über den Zustand S2 dorthin gekommen.
Das sehen wir nicht, weil wir nicht die Zustände sammeln,
sondern nur die Symbole, die emittiert worden sind.
Und die sind eben dummerweise in diesem Falle gleich.
Aber hier sehen wir es jetzt. Hier haben wir einen
dieser Y-Zyklen genommen und das nächste Ergebnis ist nicht wieder der
nächste Y-Zyklus, sondern jetzt kommt das Z-Ergebnis hier dazwischen.
Und jetzt kriegen wir tatsächlich eine vollständige Auflistung des Suchraumes,
weil wir eben nicht mehr ein Zweig des Suchraumes in die Tiefe verfolgen,
sondern immer abwechselnd mal den einen, mal den anderen.
Der andere muss in der Warteschlange warten, bis er wieder
an die Reihe kommt und dann das nächste Y-Ergebnis produzieren kann.
Und dann kommt eben wieder ein Zustand an die Reihe, der ein Z produzieren kann.
Also auf diese Art und Weise sind wir in der Lage durch recht geringen Aufwand,
nämlich nur das Auswählen,
wollen wir die Agenda so organisieren oder wollen wir die Agenda so rum organisieren,
entweder als Kellerspeicher oder als Warteschlange,
dadurch können wir einen Schalter umlegen,
der mal das eine oder mal das andere Verhalten erzeugt,
je nachdem,
wie wir
es haben wollen
Ja, und das ist auch einer der Vorteile,
dass man das hier relativ schön an einer Stelle sieht,
worin eigentlich der ganze Unterschied besteht.
Und der Preis, den wir bezahlt haben, ist eben,
dass wir hier auf diesem Argument die Suchraumagenda selbst verwalten.
Das ist jetzt unsere Verantwortung, dass das ordentlich passiert.
Das ist keine Dienstleistung des Prolog-Systems mehr. Gut, letztes
Beispiel Bäume. Was haben Bäume mit Listen zu tun?
Wir haben ja Bäume uns schon in anderen Fällen angeguckt,
wo wir binärverzweigende Bäume beispielsweise genommen hatten und dann zweistellige Strukturen der Art,
wie es von AB benutzt haben, mit linkem Zweig und Rechten,
Zweig des Baumes.
Das kann ich natürlich immer machen, auch für größere Verzweigungsfaktoren,
solange der Verzweigungsfaktor durch den gesamten Baum hin konstant ist,
wie in einem Binärbaum, der ist eben überall zwei.
Wenn ich Bäume habe, wo der Verzweigungsfaktor vaabel ist,
geht diese Lösung nicht mehr.
Dann kriege ich nämlich auf jeder Ebene des Baumes unterschiedlichstellige Prädikate.
Und sie wissen, die Stelligkeit geht immer in die Prädikatsdefinition ein.
Das ist keine praktikable Lösung. Und an der Stelle kann ich eben
listen als sehr flexibles Element verwenden, um solche Bäume zu organisieren.
Und ein Beispiel hieß sie ja angegeben, das ist ein Tri
Ein Trie ist kein Tippfehler, sondern eine Namensgebung,
die auf Donald Knuth zurückgeht, für einen solchen,
für einen lexikalischen Index, der als Buchstabenbaum organisiert ist.
Also die Idee besteht hier darin, dass sich die Wörter,
die in meinem Lexikon stehen, in ihrer Buchstabenreihenfolge organisiere.
Also das ist nur ein Teil hier bis B.
Man kann also davon ausgehen,
dass der oberste Knoten einen Verzweigungsfaktor von irgendwas wie 26 haben wird,
wenn wir 26 Buchstaben in unserem Alphabet haben
Also hier müssten noch mindestens 24 weitere Zweige hinkommen.
Und da sehen wir eben, dass wir dann mit diesen feststelligen Strukturen
an die Grenzen geraten. Dann wird es so sein,
dass zwar am Anfang die Strukturen noch relativ gut besetzt sind,
aber je weit ich in die Baumtiefe komme,
werden nur noch wenige Punkte da überhaupt besetzt sein.
Und da ist es eben besser,
ich habe dynamische Datenstrukturen wie Regulsiv verkettete Listen,
die sich da an dem Bedarf anpassen können.
Also beim A kann es dann weitergehen,
indem ich eben hier Abend in den Trieb eingetragen habe,
hier Affe eingetragen habe über diesen Weg, ist es alle
Und wir sehen, dass als teilweise auch sein kann,
dass ein Eintrag, ein Teil eines anderen Eintrags ist.
Also alle ist der Teil von alles. Ich muss also
einen expliziten Ende-Marker einführen, der mir eben sagt,
an dieser Stelle ist das Wort schon zu Ende.
Und dafür habe ich den Stern genommen, das ist reine Konventionssache.
Das kann man machen, wie man es will.
Hier in der Implementation habe ich dann tatsächlich die Lehreliste als Ende-Indikator verwendet.
Die wird sowieso vom Prolog-System für alles Mögliche an Amphilistenen-Innen verwendet.
Und dann habe ich es auch hier für diesen
expliziten Ende-Marker eingesetzt
Gut, also dieses Ding jetzt in die Listenstruktur übertragen,
sagt, ich bin eine Liste,
die als erste Alternative einen Teilbaum enthält für die Wörter,
die mit A anfangen, als zweite Alternative einen Teilbaum enthält für die Wörter,
die mit B anfangen.
Und für alle Wörter, die mit A anfangen,
habe ich hier drei Alternativen da, das ist genau diese Dreifachverzweigung,
alle Wörter, die mit B weitergehen, alle Wörter,
die mit F weitergehen, alle Wörter, die mit L weitergehen.
Und so geht das in die Tiefe runter,
mal verzweigt es sich da unten nochmal,
hier sehen wir diesen Punkt,
mit alle sind wir bis hierher gekommen,
da sind wir entweder am Ende angekommen oder es geht eben
noch weiter mit alles oder allen oder was auch auch immer für Wörter.
Hier haben wir den ähnlichen Effekt, dann mit Baum oder Bauer.
Und durch diese Baumstruktur erreiche ich zweierlei. Einerseits,
dass ich Redundanz beseitige, weil ich nämlich die gleichen Wortanfänge
nicht doppelt und dreifach und 400-fach abspeichern muss, sondern nur einmal.
Und verbunden damit ist eben ein sehr, sehr schneller Zugriff zu den Einzelwörtern.
Also wann immer man einen schnellen Zugriff hat und den Aufwand für
die dynamischen Datenstrukturen nicht scheut, dann bietet sich so etwas an.
Der Preis bezüglich der der Speicherbedarf sollte ziemlich hoch sein.
Also das kann man sich mal ausrechnen. Jede dieser Kommastellen hier
in der Liste ist eine Kunstzelle und eine Kunstzelle besteht mindestens aus zweimal vier Bytes.
Ja, also für jedes dieser Zeichen hier habe ich vier Bytes geopfert.
Acht Bytes geopfert, sorry. Und dann kann man irgendwo mal einen Break-Even-Point ausrechnen,
wo das, was ich hier eingespart habe, die Dinge,
die ich nicht aufgenommen habe, weil sie redundant sind,
ausbalanciert wird durch diesen höheren Speicherbedarf, den ich für die Einzelbuchstaben habe.
Also das ist wieder ein typisches Optimierungsproblem, wo man gucken kann,
ab welcher Besetzungsgrad eines solchen Tries sich das Ganze überhaupt lohnt.
Der muss also schon ziemlich voll sein, damit ich damit
was Sinnvolles oder dass ich dadurch Speicherplatz etwas sparen will.
So, wir gucken uns jetzt nur den einfachsten Fall an,
nicht wie die Satrie erzeugt wird,
sondern nur wie wir den Zugriff damit organisieren können.
Und dann sehen wir eben, wie wir das eben schon bei den Agendas hatten.
Wir müssen eben mit relativ komplexen Listenpattern hier umgehen können.
Also wir müssen hier Wir haben eben nicht mehr einfach schöne
lineare Listen, sondern die sind jetzt wieder rekursiv eingebettet,
tauchen dann wieder Listen auf und so weiter.
Und das haben wir jetzt hier in noch Ex-gewaltiger Form.
Wir haben auf der ersten Argumentstelle, die das Wort,
das ist eine lineare Liste, als lineare Liste repräsentiert,
so wie das hier etwa.
Und wir haben an der zweiten Argumentstelle den Tri,
das ist jetzt unsere Baumstruktur.
Und wenn wir denn im Wort am Ende Marke angekommen sind
und im Tri ebenfalls ein Ende Marke haben,
dann ist alles gut,
dann ist das Wort in dem Baum drin
Man kann natürlich, und das ist jetzt hier nicht passiert,
man kann natürlich hier noch irgendwelche Nutzinformationen anbauen,
wenn man das als Index benutzen will, dass dort ein Point da drinsteht,
wo die entsprechende Eintragung zu diesem mlexikanischen Element dann zu finden ist.
Das habe ich jetzt hier weggelassen, damit es ein bisschen einfacher wird. Ja,
falls das eben nicht die leere Liste ist,
dann haben wir hier die Liste,
die noch wenigstens ein Zeichen enthält und hier hinten noch eine Restliste von Zeichen Alternativen,
ne Quatsch,
die Restliste des Wortes und hier im Trier haben wir wenigstens noch ein Zeichen,
wo der Baum aber weiter in die Tiefe geht.
Ja, jetzt müssen wir eben genau aufpassen. Das hier ist
das aktuelle Zeichen in dem Trieb. Das ist der Pfad,
der im Baum weiter in die Tiefe geht und das hier hinten,
wo die anonyme Variable steht, das sind die alternativen Zeichen zu zäh.
Die ich möglicherweise ja auch noch überprüfen muss,
wenn sich herausstellt, das C ist dann doch nicht das Richtige.
Aber hier fordere ich eben genau die Co-Referenz,
dass das nächste Zeichen im Wort gleich dem aktuellen Zeichen in dem Trieb ist.
Und wenn das der Fall ist, dann muss ich eben
im Trieb weiter in die Tiefe gehen.
Dann habe ich das nächste Buchstabenpaar gefunden, was übereinstimmt,
dann muss es in beiden sowohl im Wort als auch im Trieb weiter in die Tiefe gehen.
Also ich nehme hier das restliche Wort Und ich nehme hier
den restlichen Trier, der nach unten geht und rufe das Ganze wieder rekursiv auf.
Ja, und wenn das eben nicht der Fall ist,
dann muss ich als Alternative nochmal das Wort unverändert nehmen und schauen,
ob eine der Alternativen, die hier hinten im Trier drin stehen,
vielleicht zu einem Ergebnis führen.
Dann rufe ich das Ganze also rekursiv nicht mit dem Baum in der Tiefe auf,
sondern mit den Alternativen in der Breite auf.
Und damit sehen wir wieder verzweigende Rekursionen. Hier gehen wir
in den Baum in die Tiefe. Das ist der
eine Möglichkeit, den Suchraum zu tranversieren.
Und hier gehen wir in den Baum in die Breite.
Das ist die zweite Dimension, in der wir
den Suchraum abarbeiten können
So, und jetzt können wir das Ganze verwenden.
Wir nehmen also einen Tree, der Tree ist auf der vorhergehenden Folie definiert worden.
Den habe ich so in die Datenbank reingeschrieben. Und deshalb
können wir ihn mit diesem Aufruf aus der Datenbank wieder rausholen.
Er spart Tippaufwand und ich möchte es ja keinem zumuten,
wiederholt, so etwas für Testzweck eingeben zu müssen.
Aber dafür hat eben das Produktsystem seine Datenbank,
die können wir ja benutzen an der Stelle.
Also wir fragen hier den Tree ab und wir wollen wissen,
ob das Wort also in dem Tree enthalten ist.
Und wenn wir nochmal uns das angucken, hier haben wir all so,
das ist in dem Tree enthalten, also sollte das Ergebnis positiv sein.
Und wir kriegen Die Vagablenbindung für den Trier heraus,
was so viel heißt wie, ja, das ist wahr für diesen Baum.
Und jetzt der Gegentest ist in dem Trier, das Wort
Aber enthalten und sie können sich davon überzeugen,
es ist nicht enthalten und dann kriege ich eben ein Fords heraus.
Das ist die typische Verwendungsweise, ich habe ein Wort gegeben,
ich möchte wissen, ist es in dem Baum enthalten, ja oder nein?
Wenn ich denn noch in dem Baum Nutzinformationen drin habe,
brauche ich natürlich noch ein weiteres Argument,
um diese Nutzinformation nach draußen zu übergeben.
Hier sind noch andere Aufrufe, das ist also die uninstanziert mit dem Wort.
Damit also die Frage, welche Wörter sind alle in dem Trieb enthalten?
Und es passiert genau das, was wir erwarten würden.
Also es wird uns immer wieder der Trie ausgegeben,
weil das eben hier eine Vagable ist, die gebunden wird.
Die Mechanismen, um dieses Binnen zu unterdrücken, hat Jan-Wiele-Makalade
rausgenommen aus der SWI-Prolog. Und wir kriegen dann über die Alternativen,
die alle Wörter aufgezählt, die sich in dem Trieb finden.
Also beginnen mit Abend A für alle und so weiter,
bis wir bei Bauer, das war unser letztes Wort,
schließlich angekommen sind
Wir können das Ganze auch vollständig unterspezifiziert aufrufen,
indem wir eben Fragen, gib mir alle Trees,
indem das Wort all so enthalten ist.
Und wir kriegen dann dieses Pad dann hier.
Und wenn man sich das genauer untersucht,
dann ist das tatsächlich der Raum von Möglichkeiten, den wir haben,
nämlich entweder steht das Wort also da komplett drin oder es ist das Prefix eines Wortes,
was noch weitergeht.
Das sind die beiden Alternativen. Also entweder das Wort ist hier
zu Ende oder aber es geht noch weiter in die Tiefe.
Ich weiß noch nicht,
wie bis jetzt
Das ist nicht zu verwechseln mit einem Programm, was ein Wort
in einen Trieb einsetzt. Das ist eine andere Aufgabe.
Das mache ich manchmal in der Übung in diesem Jahr aber nicht.
Wie man jetzt tatsächlich so einen Baum zu konstruieren hat.
Und wie man neue Wörter in den Baum einfügen kann. Denn das,
was hier rauskommt, ist ja nur ein Pattern,
was so allgemein ist, dass alle Bäume,
die also enthalten können, damit erfasst sind.
Und ich habe jetzt keine Möglichkeit zu sagen, ja,
was passiert denn,
wenn ich jetzt nicht das Wort all so in dem Baum haben will,
sondern zusätzlich noch das Wort Butterbrot.
Dann kann ich das nicht mit diesem Prädikat Wort machen,
was wir hier eben definiert haben.
Das funktioniert nicht
Das muss man, da muss man mehr Aufwand treiben.
Der führt jetzt aber zu weit, das werde ich nicht mehr behandeln.
So, damit sind wir wieder an einem einschneidenden Punkt angekommen.
Jetzt ging es eigentlich in der Logik der Veranstaltung weiter mit den
aktiven Datenstrukturen. Ach so, wie ich das sage,
wollte ich noch die Bemerkung loswerden. Was wir hier sehen,
deshalb habe ich den Pest nur aufgenommen, ist,
wir sind im Bereich der Listenprogrammierung und das habe ich ja schon mehrfach behauptet,
dass es der Bereich, wo die Logikprogrammierung zu Hause ist,
rekursive Datenstrukturen mit rekursiven Programmen verarbeitet.
Und wir sehen auch hier, wir haben keine Terminierungsprobleme.
Das geht in allen Instanzierungsvarianten. Wir haben keine Probleme mit
Mit ungenügend instanziiertem Variablen, wie wir das etwa im Falle der Arrithmetik haben.
Also wann immer wir in dem Bereich sind, da ist die Welt heil.
Da kann man ziemlich sicher davon ausgehen, dass alle Instanziierungsvarianten tatsächlich funktionieren.
Oder andersherum, wenn man eine Instanziierungsvariante findet,
die nicht richtig funktioniert, die also in einen Zyklus läuft,
dann hat man bei der Definition mit Sicherheit irgendetwas falsch gemacht.
Da muss man nochmal gucken, wo die Ursache dafür eigentlich liegt.
So, jetzt also zurück zu den aktiven Datenstrukturen, die kämen jetzt dran.
Es ist für mich das Kapitel im Gang der Dinge,
was ich am Interessantesten findet, neben der Meta-Programmierung,
auf die wir noch kommen werden.
Es ist der Bereich, wo die Logikprogrammierung ihre ganzen Vorzüge
tatsächlich richtig ausspielen kann. Das ist das Alleinstellungsmerkmal,
was ich in keinem anderen Paradigma zur Verfügung habe.
Ich schiebe es nach hinten aus zwei Gründen. Einerseits,
weil man sich natürlich die schönen Sachen immer bis zum Schluss aufheben soll.
Andererseits aber auch, weil wir ja noch einen Blick in die funktionale Programmierung tun wollen.
Und da ich eh die Klausur kommt, möchte,
dass wir in der Übung nochmal die funktionale Programmierung ansprechen können,
muss ich die zeitig genug hier drin haben?
Also wir machen jetzt den weiteren Gang durch das Skript durch
Bis zur funktionalen Programmierung. Und ich komme dann anschließend wieder auf die aktiven Datenstrukturen zurück.
Das ist also jetzt ein großer Sprung, der passiert.
Und wir machen mit dem Thema der Extra- und Metaph-Prädikate weiter.
Und das ist im Wesentlichen ein Abschnitt, na,
ja, da sind wir. Das ist ein Abschnitt,
wo sehr viel manuell Wissen zu vermitteln wäre, was erstens,
wo eine Vorlesung für systematisch ungeeignet halte, so etwas zu verbreiten.
Also was ich hier erzähle, dient im Wesentlichen den Zweck,
dass sie das mal gesehen und gehört haben,
dass sie wissen, so etwas gibt es.
Und wenn man das wirklich mal braucht, dann muss man
das Handbuch öffnen und gucken, wie funktioniert es wirklich.
Ich werde jetzt also diese ganzen technischen Details,
die hier dahinter stecken, nicht im Einzelnen ausbreiten.
Das verbindende Element, oder dass die wichtigen Sachen sind eigentlich
illustriert durch diese Lupe hier. Es gibt uns die Möglichkeit,
genauer hinzuschauen, was macht die Verarbeitung von Prolog eigentlich?
Also bis jetzt ist ja die einzige Möglichkeit zu sehen,
ist ja der Trace gewesen. Wir haben den Drehs angeschaltet
und gesehen, wie werden dort Prädikate in welcher Reihenfolge aufgerufen?
Wie werden Vajablen gebunden? Wo kommen die Ergebnisse eigentlich her
? Jetzt kriegen wir, das ist sozusagen ein Werkzeug,
mit dem ich auf die Gesamtabarbeitung schauen kann.
Jetzt kriegen wir Werkzeuge, wo wir auf die Details der Abarbeitung
schauen können und die wir in die Prädikatsdefinition mit einbauen können,
um unerwünschte Effekte zu vermeiden. Also das geht damit los,
dass wir natürlich Mittel und Möglichkeiten brauchen, um Typen und Typkonversionen,
also Typen zu überprüfen, ob die Typen, die Zusicherungen,
die wir gemacht haben, ob die auch eingehalten werden.
Natürlich auch Möglichkeiten, unterschiedliche Typen ineinander umzuwandeln,
also der ganze Bereich des Castings
Dann der Bereich der kontrollierten Instanzierung. Das ist wieder ein kleines,
ein kleiner Blick da rein, dass wir dem Prolog-System die
Kontrolle aus der Hand nehmen und in die Eigenregie übernehmen.
Dann entscheiden wir, ob eine Variable instanziert werden soll oder
nicht und nicht mehr die Unifikation.
Also was wir uns hier noch nicht angucken, ist,
wie wir das dem Prolog-System aus der Hand nehmen.
Wir beschreiben hier nur erstmal die Mittel,
die wir brauchen, damit wir es dann können.
Die eigentliche Meta-Programmierung kommt im Abschnitt danach. Dann werden wir
die Sachen, die wir hier einführen, tatsächlich benutzen,
um Prolog-Systeme mit neuer Semantik zu generieren, die eine abweichende Semantik haben.
Und das kulminiert dann darin, dass wir das Prolog-System umbiegen,
das ist Geam-Programme interpretieren kann
Und dann benutzen wir es als Stream-Interpreter, um funktionale Programmierung zu betreiben.
Das ist der Gang der Dinge, die wir in den nächsten Wochen vor uns haben.
Wir haben Mittel der Suchermanipulation, das haben wir schon mehrfach angesprochen.
Das ist also die Möglichkeit, Teile des Suchraumes abzuschneiden,
wenn man sie nicht besuchen möchte oder wenn dort ja meistens nicht möchte.
Oder wenn es unnötig ist, sie zu besuchen,
weil man schon von vornherein weiß, dass dort kein Ergebnis ergeben kann.
Und dann kommt noch der gesamte Bereich der extra logischen Prädikate.
Das ist also sowas wie Lesen, Schreiben und dergleichen mehr,
die werde ich überhaupt nicht behandeln. Also von der von dem,
was ich vorhabe, einerseits die metalogischen Prädikate metalogisch heißt immer,
ich schaue aus einer höheren Perspektive auf die Abarbeitung des Logikprogramms drauf und entscheide dann in Abhängigkeit von dem,
was ich da sehe, wie es weitergehen soll.
Das sind also die Sachen hier, sie sehen Trace und die Bug habe ich hier rausgenommen,
weil das nichts ist, was ich ins Prädikat selbst einbauen kann,
sondern nur die Elemente, die tatsächlich im Prädikat selbst benutzt werden können.
Und von den extralogischen Prädikaten, das sind also alles die,
die mit der Logik überhaupt nichts zu tun haben, sondern nur noch mit dem Computer. Also sowas wie Schreiben,
Lesen, sowas wie Interface und Betriebssystem lasse ich hier raus,
alles, was ich behandle und das haben wir eigentlich schon gemacht,
ist, wie kann ich die Datenbank manipulieren und auch ein Mechanismus,
den es gibt und den sie auch schon gesehen haben im Zusammenhang mit dem mit dem Verzeichnisaufbau ist die Verwendung globaler Variablen in Prolog,
die eigentlich mit der Logik nichts zu tun haben, überhaupt nichts,
aber den Speicher, den ich nun mal zur Verfügung habe,
tatsächlich auch auf der Logikebene zur Verfügung stellen.
Also wir haben natürlich Datentypen,
wie überall in jeder Programmiersprache,
die haben wir alle schon gesehen, die ganzen Zahlen,
die Gleitkommazahlen, wir haben Atome,
die haben wir bisher immer mit Namen bezeichnet und wir haben noch einen weiteren Datentyp,
den haben wir bisher überhaupt noch nicht behandelt,
den habe ich hier SWI für SWI-Prolog drin genannt,
er ist aber tatsächlich Bestandteil des ISO-Standards auch
Das heißt also, das, wo immer hier SWI-String steht,
können Sie auch ISO-Standard hinschreiben, für internationale Standardisierungsorganisationen.
Prolog ist standardisiert und das ist einer der standardisierten Datentypen.
Komme ich gleich noch dazu. Wir können aus den Basisdatentypen,
die wir hier haben, abgeleitete Datentypen bilden.
Also Number ist eben die Vereinigung von Interter on Float.
Atomic ist die Vereinigung von Atom und String und Zahl.
Also das sind die atomaren Datentypen, die wir haben.
Das sind die numerischen Datentypen,
die wir haben
Man kann so etwas eben verwenden als Guard. Wir hatten ja
den größten gemeinsamen Teiler, wenn wir dort die Zusicherung nicht einhalten,
also das Ganze mit positiven Intager-Zahlen aufrufen, dann kriegen wir natürlich Probleme.
Deshalb können wir hier also einen Guard einbauen,
der uns dafür sorgt, dass die Eingabedaten, also X und Y,
das sind nämlich die, die mit dem Plus hier versehen sind,
dass die tatsächlich Intager-Zahlen sind und zwar positive indeutscher Teile.
Ansonsten scheitert das Ganze, wenn wir das jetzt also aufrufen,
wie wir das da dünnemals mit den Tests gemacht haben,
hier mit irgendwelchen gebrochenen Zahlen,
dann kriegen wir eben Falls heraus,
weil es ihr an diesen Typtests
bereits scheitert
Der Datentyp String, dazu muss man ein bisschen was erklären.
Ursprünglich, oder nein, fangen wir so an.
Ich habe es ja schon mehrfach erwähnt,
in der Listenprogrammierung, da ist die Logikprogrammierung zuhause.
Dort kann ich große Zusicherungen machen, was die Terminierungssicherheit der Prädikate betrifft.
Und deshalb ist in den ursprünglichen Prolog-Varianten, sind Strings auch
als Listen repräsentiert worden. Also wenn Sie im klassischen Edinburgh Prolog,
das ist nicht der Urvater aller Prologsysteme,
aber der de facto Standard, der sich dann entwickelt hat,
wenn man das zugrunde legt und dort eine Variable als Zeichenkette mit einer Variable, eine Zeichen
Eine Unifikation zwischen der Variablen und einer Zeichenkette herbeiführt,
kriegt man als Wert dieser Variablen hinterher eine Liste ausgegeben.
Und das ist die Liste der Aski-Werte dieser einzelnen Zeichen.
Also schon beim Einlesen werden Zeichenketten standardmäßig in Listen umgewandelt,
dann bin ich in der Listenwelt und kann alles das machen,
was ich eben mit Listen so schön machen kann.
Wenn ich also so etwas habe wie,
ich lese das als Zeichenkette ein und mache anschließend den Typtest,
den wir eben eingeführt hatten da drauf,
dann kriege ich Fords heraus, weil das eben eine Edinburalis,
ein Edinburals-String ist und String testet auf ISOS dringen.
Und das ist kein ISOS-Dring, also kommt hier Fords
Dabei raus. Diese Implementierung von Strings als Listen ist logisch sauber,
aber entsetzlich teuer. Die Rechnung hatte ich ja vorhin gerade aufgemacht,
pro Zeichen müssen wir acht Byte investieren,
damit wir das als Liste verwenden können.
Das ist ein Faktor von 1 zu 8, der teilweise etwas weh tut.
Deshalb hat also die ISO-Norm als separaten Datentyps dringend eingeführt.
Und wenn ich jetzt also unter der ISO-Norm eine, die gleiche Unifikation vornehme,
dann kriege ich als Variablenbindung tatsächlich dann wieder den Datentypstring heraus.
Und wenn ich den gleichen Test jetzt mache,
ich binde diese Variable an, einen String,
ich teste, ob das ein String ist,
dann kriege ich tatsächlich einen Truh heraus,
es ist ein String
Vorher kam an der Stelle ein Falls. Das SWI-Prolog erlaubt jetzt,
beides zu benutzen. Und dafür gibt es einen Schalter,
den ich umschalten kann, der mit Stylecheck aufgerufen wird.
Und wenn ich Stylecheck mit plus String aufrufe, dann wird der ISO-String aktiviert.
Das ist die Standard-Variante, die man im SWI-Prolog hat.
Man muss im Gegenteil den ISO-String ausschalten, wenn man
es haben will. Aber wenn er denn eingeschaltet ist,
dann kriege ich genau das Verhalten, was wir eben gesehen haben,
dann ist das eben auch im Output eine richtige Zeichenkette,
die eben mit einem Zeichen pro Byte abgespeichert wird.
Und wenn ich den ISO-String ausschalte, dann kriege ich den alten Edinball Standard,
der also die Zeichenkette mir wieder als Liste von Aski-Werten ausgibt.
Also über dieses Prädikat kann ich zwischen den beiden Varianten hin und her schalten.
Wem das eine mir gefällt oder wer sich glaubt,
leisten zu können, der kann das neben der andere,
das andere steht auch zur Verfügung.
Bei den, wir haben einerseits die atomaren Datentypen,
andererseits die plexen Datentypen, also Strukturtypen,
wo die zusammengesetzt sind aus atomaren Datentypen,
wobei String ein atomarer Datentyp ist.
Darüber muss man sich im Klaren sein,
das ist Atommake
Strukturtypen haben wir eben im Wesentlichen Therme, die wir mit Compound testen können.
Also Compound ist alles das, was nicht Atomic ist.
Also insbesondere alles das, was Forn mit einem Funktor beginnt.
Und da wir ja gesehen hatten, dass Listen, nein,
wir haben es nicht gesehen, ich habe es ihnen gesagt,
dass Listen auch nichts anderes als Strukturen mit dem Funktor Punkt sind,
zählen also auch Listen da dazu.
Aber weil Listen eben eine häufig gebraucht werden,
gibt es natürlich die entsprechenden Typtests auch für Listen.
Und zwar, das hatte ich schon mal angedeutet,
muss man immer unterscheiden, Populist ist etwas,
was eine Liste durchgeht Und bis zum letzten Element prüft,
ob das eine wohlgeformte Liste ist.
Das heißt also auch, ob sie mit einer leeren Liste
beendet wird, mit einem Listen-Ende-Marker.
Das ist eine Popperlist. Ist List, guckt nur auf
der obersten Ebene, ob dort ein gepunktetes Paar vorliegt.
Wenn ja, dann ist alles okay. Es guckt nicht in die Tiefe der Liste,
geht nicht rekursiv dort runter, um zu schauen,
ob auch das, was weiter unten sich in der Liste findet,
immer noch eine Liste ist.
Das wird also hier nicht gemacht. Das guckt nur auf
der obersten Liste. Listen-Ebene nach, ob das so aussehen,
vom Außen her aussieht wie eine Liste.
Also man kann es vergleichen, das ist der Test von außen,
ob unsere Matroschka, eine Matroschka ist.
Und das ist der Test, der wirklich bis ganz innen reinguckt,
ob bis ganz zum Schluss dort Matroschkas sich drin befinden oder irgendetwas anderes.
Gut, SZ ist dann das Gleiche wie Popalist.
Das hat man ja in der Übung gerade auch,
wo ich die Liste rekursiv durchgehe und schaue, erstens,
ob es Duplikate in der Liste gibt und SZ erwartet,
dass es keine Duplikate gibt.
Aber wenn es nach den Duplikaten guckt,
dann ist ein Nebeneffekt natürlich, dass es eine Popaliste sein muss.
Denn das terminiert nur, wenn es zum Schluss bei der Lehrenliste
ankommt, nur dann ist das Ganze erfolgreich.
Also ist sozusagen der Listentest ist ein Gratisleistung,
die uns das ISZ mit nebenbei liefert. Wir können natürlich
Typen konvertieren, auf der Arrithmetik-Ebene gibt es eben Funktionen,
die eine Gleitkommazahl machen. Also ich kann etwa sagen,
Float von zwei und dann wird die zwei in eine Gleitkomma-Repräsentation überführt,
obwohl es auch eine Intager-Zahl sein könnte.
Oder aber ich kann eben etwas nach Intager casten.
Das ist also wie in allen Programmiersprachen. Interessanter sind die Struktursachen,
Ich habe saalreiche Prädikate, wo ich zwischen Atomen und Strukturen hin und her operieren kann.
Also Atom Code, es gibt mir eben genau diese,
für Atome jetzt, nicht fürs Drinks, Vorsicht,
Atome und Strinks sind zwei unterschiedliche Sachen.
Das gibt mir für Atome die Liste aller Asky-Werte,
die in diesem Atom drin sind.
Das Gleiche für Name ist eben für beides. Das geht
also sowohl für Atome als auch für Intagers. Hier ist die gleiche Variante,
dass ich eine Intager in einen Atom umwandle,
also wirklich in ein Atom, was nur aus Ziffern besteht,
Das hier nimmt nur ein einstelliges Atom und wandelt das in den entsprechenden Aski-Wert um.
Ich kann auch ganze Therme, also mit Klammer auf,
Klammer zu und so weiter, in einen Atom umwandeln.
Auch das geht, wenn ich das,
ich habe noch nie so was gebraucht,
aber das ist so die Werkzeugkiste, wo alles Mögliche drin ist.
Man steht staunend davor und sagt sich, ja, nett,
dass es das gibt, aber wofür brauche ich das eigentlich?
Ich kann natürlich Zeichenketten in Atome umwandeln oder ich kann
auch Zeichenketten in Listen wieder umwandeln.
Und da gibt es also Implementationsabhängig auch,
das ist der Bereich,
wo es in jedem System ein bisschen
eine andere Ausstattung zu finden ist
Hier nur ein Beispiel, dass ich eben,
wenn ich hier dieses Atom-ABC eingebe und Adam Codes darauf anwende,
dann kriege ich eben die Liste dieser Aski-Werte für A, B und C.
Das Ergebnis, was hierbei rauskommt, ist eben kein Stringen,
sondern es dringend testet auf ISOS dringen.
Und das ist ein Adam Boro-String und deshalb scheitert dieses String hier.
Man kann die Dinge auch wieder in verschiedene Richtungen benutzen,
also hier String to Atom,
beispielsweise habe ich hier mit dem Atom instanziert und dann kriege ich
die entsprechenden String ausgegeben und das ist tatsächlich dann ein Stringen
Also nachdem ich es von Attem nach String gecastet habe,
dann kann ich den Test auf Stringhaftigkeit machen.
Und dann kriege ich die Antwort Truhe heraus.
Also da muss man ein bisschen aufpassen. Strings sind nicht gleich
Atom, obwohl sie manchmal so ähnlich aussehen. Ja,
da unterscheiden sich dann die Reitprädikate auch wieder noch,
ob sie die Hochkommers mit ausgeben oder nicht.
Da muss man in die Details, in die technischen Details dann einsteigen.
Es gibt eine Reihe von Zugriffsfunktionen oder Prädikaten für die typspezifisch sind,
also für Atome kann ich mir etwa die Länge ausgeben lassen.
Ich für Strings ist das Gleiche. Ich kann mir Substrings ausgeben lassen,
ähnlich wie wir das für die Listen gemacht haben,
nur dass man hier eben keine Liste eingibt,
sondern eine Eine Liste, ein ISOS-Drängen.
Für Listen hat, das haben wir schon gesehen, das ist nichts Neues.
Letztes Element, Member eines Elements, Endes-Element eines Liste,
Länge einer Liste und so weiter und so fort.
Das ist der ganze Zoo, den wir schon durchgesprochen haben.
Ich kann Typen konstruieren für Atome beispielsweise,
kann ich Atome aus einer Liste von Zeichen zusammensetzen.
Ich kann das Gleiche für Zahlen machen. Ich kann Atome miteinander verketten,
indem ich einen Atom nehme, zweites Atom Neues rausbekomme.
Das ist im Prinzip, das Pengdang zu erpennen,
auf der Listenebene, nichts anderes. Ich kann das auch zweistellig aufrufen,
indem ich hier eine Liste von Atomen vorgebe und dann werden alle
diese Atome in allen Atomen zusammengepackt als Ergebnis zur Verfügung gestellt.
Ja, etwas, was wir brauchen werden,
sind auch in der Übung, das kommt auf einem der nächsten Zettel,
ist die Umwandlung von Thermen in Listen.
Therme haben eine ganz wesentliche Einschränkung und das macht ihre Nutzung
manchmal ein bisschen problematisch. Die Einschränkung besteht darin,
dass hier vorne an der Funkdorstelle keine Variablen verwenden darf.
Ich kann also, hier war Jablin verwenden und hier war Jablin, aber hier nicht. Wenn ich jetzt also einen Therm konstruieren will,
wo ich den Funkdor, je nach Verwendungszweck setzen will oder nicht setzen will,
brauche ich einen Umweg, dann muss ich als erstes sagen,
ja, ich möchte Funktor F haben,
ich möchte die Argumente A und B haben,
dann wandle ich das Ganze in eine Liste um oder ich baue eine Liste auf und dann nehme ich diesen Operator und mache daraus das F muss jetzt natürlich instanziert sein.
Also sagen wir mal, hier so F gleich P.
Und dann macht das das daraus, den Therm P von AB.
Das ist ein Workaround, den ich brauche,
weil ich hier vorne nicht über die Unifikation rankomme.
Der Funktor ist vorgegeben und aus gutem Grund.
Ansonsten wäre die gesamte Prolog-Abarbeitung völlig unineffizient. Über den Funktor
wird gehashed. Da wird geguckt, dass ich,
wo was in der Datenbank steht.
Meistens nicht nur über den Funktor, sondern auch noch über das erste Argument.
Aber der Funktor auf jeden Fall ist unverzichtbar.
Wenn ich den aufgebe, bleibt das ganze System stehen
So, und die Verwendungsweise sieht man eben hier,
ich habe hier einen Term gegeben und man kann es sich merken,
dort wo die beiden Punkte sind, ist die Listenseite und dort,
wo die Punkte nicht sind, da ist die Termseite.
Und dann wird eben dieser Term hier in eine Liste umgewandelt,
in dem der Funkter das erste Listenelement ist und
die Argumente dann die folgenden Elemente der Liste sind.
Und ich kann das Ganze umgekehrt aufrufen,
indem ich sage,
hier wandle mir diese Liste ABC in einen Term um und
dann wird das erste Element zum Funkto gemacht und die
restlichen Elemente zu den Argumenten des Terms
Und auf diese Art und Weise kann ich jetzt nach belieben
und davon werden wir bei der Scream-Interpreter massiv Gebrauch machen kann.
Auf die Art und Weise kann ich jetzt Prädikate generieren
und kann die sofort wieder in die Verarbeitung reinstecken.
Ich konstruiere ein Programm und sage dann sofort Call und gib mir bitte
den Wahrheitswert für die Anwendung oder auch die Vayablenbindung für die Anwendung einer Klausel.
Nächstes Thema ist Vayablen-Inspektion. Wir hatten ja ganz oft,
dass es in bestimmten Instanzierungsvarianten Terminierungsprobleme gibt,
wenn ich eine Vayable nicht instanziert habe oder aber wo Ich einen Fehlernachrichten kriege,
wo die Verarbeitung abbricht.
Das ist ja auch äußerst lästig für einen Lauf in das Programm.
Wenn das abbricht, nur weil irgendwo eine Variable nicht instanziert war,
mit diesem Var- und Nonvard-Prädikaten, die auch eingebaut sind,
kann ich den Instanzierungszustand einer Variablen überprüfen.
Also wenn ich so etwas frage, wie ist das eine Variable,
dann kriege ich eben einen True heraus. Wenn ich frage,
ist dieses kleine A eine Variable, dann kriege ich einen False heraus.
Und das logische Pangdang dazu ist natürlich die Negation davon,
ist dieses große X eine keine Variable, dann kriege ich False heraus,
denn es ist ja eine und ist dieses kleine A,
eine Nicht-Vagable, dann kriege ich Truh heraus,
denn es ist keine Vagable.
Auch wenn ich einen Therm eingebe, ist das keine Vagable,
auch wenn der Therm noch intern Variablen enthält.
Er ist auf der äußersten Ebene ein Therm und damit keine Vagable mehr.
Das heißt also, hier kann ich Nonvar von diesem Therm,
liefert mir ein Truh. Das kann ich allerdings testen,
mit dem Predikat Ground, in dem ich also sage,
Also alle Therme, die noch Variablen enthalten sind, non-grauend.
Und alle Therme, die Variablen frei sind, sind grauend.
Das ist eine Definition, die wir ganz zu Anfang mal hatten.
Und hier ist das zugehörige Prädikat, mit dem man es
testen kann. Also der Test hier auf diesen Thermen,
der keine Variablen enthält, für Ground ist True.
Hier ist eine Variable, das ist die gleiche Struktur,
die wir eben hatten. Die Variable X in dem Therm
enthalten. Dann kriege ich natürlich Fals heraus,
weil eben noch eine nicht instanzierte Variable vorhanden ist.
Also es ist immer die Frage nach dem Instanzierungszustand.
Es ist nicht die Frage, ob die Variable,
das Variablen-Symbol hier steht,
sondern ob dieses X einen Wert hat oder nicht,
das wird überprüft
Wenn das X vorher mal instanziert worden ist, dann ist es natürlich
wieder grauend. Und hier oben haben wir das Gleiche,
die Variable selbst, ist natürlich dann auch nicht grauend,
denn es ist ein Term, der nur die Variable enthält.
Auf diese Art und Weise können wir jetzt Prädikate,
die an sich nicht relational sind,
wenigstens teilweise relational machen und die Implementierung im Prologsystem ist an einigen Stellen auch so,
dass sich solche partiell relationalen Definitionen habe, an Stellen,
wo man es eigentlich nicht erwarten würde.
Also wir hatten ja unser Additionsprädikat mit zwei Somanten gegeben und der Summe,
die ich als Ergebnis haben möchte. Und das Problem ist eben,
wenn ich das einfach jetzt mit X plus Y implementiere,
in dem Augenblick, wo entweder X oder Y nicht instanziert ist,
kriege ich den Fehler,
dass es die Vayablen nicht hinreichend gut instanziert sind.
Und ich möchte aber ein Verhalten dieser Art haben,
dass also hier auch Uninstanzierte Vayablen auftreten können.
Und das kann ich eben jetzt die verschiedenen Berechnungsrichtungen der Funktion,
denn die Addition ist eine Funktion, die kann ich jetzt ausbuchstabieren,
indem ich sage, ja,
wenn denn X eine Konstante ist und Y ist eine Konstante,
dann ist es
die klassische Addition von X plus
Wenn denn X eine konstante ist und Z ist eine konstante Y,
ist also unterspezifiziert, dann muss ich subtrahieren.
Und hier im umgekehrten Fall, dann ist Y eben die Konstante,
dann muss sich das X ausrechnen, ebenfalls durch Subtraktion von der Summe.
Natürlich ist das nicht vollständig, relational,
wenn ich alle drei unterspezifiziert lasse, was passiert?
Oder es reicht auch schon,
wenn ich zwei unterspezifiziert lasse, dann scheitert es eben schon
an diesen Tests hier
Es gibt keine Variante, wo die erlaubt, dass zwei Variablen uninstanziiert bleiben.
Es müssen immer zwei instanziiert sein. Das heißt, das wird
tatsächlich nicht funktionieren. Und das sehen wir eben hier,
wenn ich jetzt also aufrufe 3 plus 4, dann kriege ich,
das ist kanonische Addition, wenn ich eben sage,
was muss ich zu X addieren, damit wir rauskommen,
kriege ich das Ergebnis 3.
Da sieht alles gut aus. Wenn ich aber frage,
was kann ich, welche zwei Seumanten kann ich addieren,
sodass sieben herauskommt, dann scheitert das,
dass zwei uninstanziierte Variable sie nicht erlaubt.
Könnte ich natürlich auch implementieren. Also es ist ja nicht
so, dass es nicht geht, ist natürlich dann mit
erheblichem Aufwand verbunden
Den habe ich hier nicht getrieben. Ich habe es nur für
eine uninstanzierte Variable gelassen. Das ist ein etwas trickreiches Gebiet,
wo ich auch immer noch mal,
wenn ich denn an der Stelle mal ankomme,
ins Manual gehen muss und die Details mir angucken muss,
was da wirklich passiert.
Wir haben ja Unifikation als die Möglichkeit, zwei Therme auf Verträglichkeit
zu überprüfen, eingeführt. Und es gibt schärfere Bedingungen,
mit denen ich Therme auf Verträglichkeit überprüfen kann,
die den Vergleich vornehmen, ohne dass dabei unifiziert wird.
Ulifikation ist ja, zwei Therme gleich machen.
Hier wird nur geprüft, ob sie miteinander verträglich sind
Ohne dass dabei die Unifikation tatsächlich ausgeführt wird.
Die beiden verschärferen Varianten sind einerseits strukturelle Identität und strukturelle Gleichheit,
strukturelle Identität ist der schärfste Test, den wir haben.
Der fordert, der Typ muss gleich sein,
der Funktor muss gleich sein, die Schnelligkeit muss gleich sein.
Und die Argumente müssen gleich sein. Wenn es denn Vajablen sind,
dann müssen sie den gleichen Namen haben oder vorher mal unifiziert worden sein.
Und das sind eben Beispiele davon, konstant auf Konstantes. Richtig,
konstant auf Vajable, das ist eben schon nicht gleicher Typ,
ist False, A von B, gleich A von BS2,
weil eben gleicher Typ, gleicher Funktor,
gleiche Stelligkeit,
gleiche Argumente
A, von X und A, von X,
also jetzt große Variable X ist auch True,
weil die Variablen den gleichen Namen haben, A,
von X und A, von Y,
ist nicht äh wahr, ist False,
weil sie einen unterschiedlichen Namen haben.
Also hier ist der Unterschied zur Unifikation. Die Unifikation würde
hier sagen, ja, unter X gleich Y, okay.
Dieser strukturelle Identität lehnt es ab. Wenn ich allerdings vorher
X mit Y mal unifiziert habe,
dann ist auch für diesen Ausdruck hier,
also ist auch für die beiden aus Therme,
die Struktur der Identität gegeben
Ein etwas schwächerer Test ist die strukturelle Gleichheit, die,
dann sind Vagabel auch dann gleich,
wenn ich sie durch Umbenennung gleich machen kann.
Also wenn es der gleiche Chorreferenzbeziehung sind, die sich ergeben,
der interessante Punkt ist also dann nur hier unten.
Hier kann ich eben durch Umbenennung von C und D
und zwar konsistente Umbenennung das Ding gleich machen.
Dann würde es die strukturelle Identität wieder erfüllen,
wenn ich denn C nach A umbenenne und D nach B umbenenne.
Und zwar überall in diesem Ausdruck, dann ist strukturelle Identität
wieder gegeben
Und es gibt eben diese Hierarchie,
Unifizierbarkeit ist die schwächste oder auch allgemeinste, wenn man so will,
Forderung, die ist in speziellere und damit starke Einschränkende Forderungen.
Auch hier nur zur Information, ja, ich ahne schon
die besorgte Frage, müssen wir das in der Klausur können?
Da kann ich mit Sicherheit sagen, nein. Ich werde
so etwas nie, ich muss auch nachgucken, ja,
ich schlage immer wieder nach, wie war denn das nun genau?
Ich kann mir das auch nicht merken. Etwas,
was wir auch brauchen werden,
wenn wir dem System die Unifikation aus der Hand nehmen wollen,
ist, dass wir das Kopieren selbst in die Hand nehmen.
Also Unifikation bedeutet, ist eine destruktive Operation und erfordert immer,
dass sich die Therme, die ich vorher habe, erst mal kopiere.
Das ist der Grund, weshalb wir diese merkwürdigen Variablen ausgegeben
bekommen, wenn wir einen Trace machen.
Weil als allererstes die Dinge umbenannt werden,
um einen neuen Therm zu erzeugen mit frischen Variablen,
die an keiner anderen Stelle im System benutzt werden.
Und das müssen wir dann irgendwie selber in die Hand nehmen.
Und dafür gibt es diesen Aufruf Copy-Therm,
der nimmt eben einen Therm her, erzeugt einen strukturgleichen Therm,
der allerdings Variablen enthält,
die eben ursprünglich nicht enthalten waren
Damit haben wir eine Kopie erzeugt. Jetzt können wir
mit dieser Kopie machen, was immer wir wollen,
ohne dass der Originale, die originale Struktur verändert wird dabei.
Wenn wir das auf der originalen Struktur machen würde,
schlägt sofort die Unifikation zu und instanziert die Variablen,
dann ist es passiert. Das wollen wir verhindern.
Wir wollen dem System die Unifikation aus der Hand nehmen.
Wir wollen es in die eigene Regie übernehmen.
Und dazu müssen wir den Termen erstmal kopieren, um dann
auf der Kopie arbeiten zu können und den Originalthermen unangetastet zu lassen.
Eine Möglichkeit ist auch, sich tatsächlich mal die ungebundenen Variablen
in einem Thermen geben zu lassen, um etwa die Variablenbindung dann verwalten zu können.
Also ich gebe einen Thermen vor und ich kriege eine Liste
aller freien Variablen, die in diesen Thermen auftauchen. Gut,
ich kann auch uninstanzierte Therme auf andere Art und Weise erzeugen,
nicht nur durch Kopieren, sondern tatsächlich aktiv durch Konstruktionen und dafür gibt es zwei Prädikate.
Das erste ist Funktor. Das sagt mir,
entweder wir sehen es in allen Instanzierungsvarianten verwendbar,
entweder ich habe einen Funktor gegeben und eine Stelligkeit gegeben,
dann erzeuge mir einen Thermen daraus oder ich habe einen Thermen gegeben,
dann erzeuge mir einen Dann sage mir, welchen Funktor hat er,
welche Stelligkeit hat er?
Also hier die analytische Verwendungsweise,
ich gebe den Thermen vor und ich kriege eben raus,
das ist ein Therm mit einem Funktor A und der Stelligkeit 1.
Und hier die umgekehrte Variante erzeuge mir einen Funktor mit der
Stelligkeit 2 und dem erzeuge mir eine Struktur mit der Stelligkeit
2 und dem Funktor A und ich kriege genau das heraus.
Und wir sehen eben hier, das ist ein Uninstanzierter Therm,
der enthält nur ungebundene Variablen.
Auf die Art und Weise kann ich jetzt wieder Therme erzeugen,
die in die Unifikation hineingehen können,
ohne dass an irgendeiner anderen Stelle einen Nebeneffekt erzeugt wird
Wenn ich den Funk da manipulieren kann, will ich auch die Argumente manipulieren.
Hier gebe ich also die Stelle vor, über die ich spreche,
also das erste Argument und einen Term und ich kriege dann den Wert,
der an dieser Stelle des Terms steht.
Also in diesem Falle das B. Auch das kann ich umgekehrt verwenden.
Ich kann also die Argumentstelle und den Wert vorgeben und kriege
dann den Term heraus, der die Bedingungen erfüllt.
Das ist wieder in allen Varianten verwendbar. Und auf die
Art und Weise kann ich jetzt mir beliebiger Term zusammenbasteln,
indem ich sage, ja, ich brauche mal einen Term,
der ist zweistellig und hat den Funktor A.
Und dann möchte ich,
dass das erste Argument gleich B ist und dann möchte ich,
dass das zweite Argument gleich zäh ist
Und zwar in diesem Therm X, ja,
ich rede immer über den gleichen Therm X und dann
wird eben hier B und hier C hinein instanziert.
Das würde ein Therm mit zwei ungebundenen Variablen erzeugen und die beiden Aufrufe sorgen dafür,
dass die Variablen hier in dem Therm tatsächlich gebunden werden.
Also es ist eine partielle Unifikation, die ich hier erzwinge,
ja, unifiziere das erste Argument in diesem Therm mit dem Ding.
Also ja, nicht mehr die gesamte Unifikation für den gesamten Therm,
sondern nur an einer ganz bestimmten Stelle soll irgendetwas
manipuliert werden
Also sie sehen, da kriegt man ziemlich mächtige Werkzeuge in die Hand,
mit denen man allen möglichen Tricks machen kann,
um von der Standardabarbeitungsreihenfolge des Produktsystems abweichen zu können.
Und teilweise nicht alle, aber einen großen Teil dieser Tricks
werden wir dann tatsächlich verwenden. Das ist der große Bereich gewesen,
vajablen Manipulation, also einerseits, wir überwachen, wir schauen,
ob die Vagablen instanziert sind oder nicht instanziert sind,
bis hin zu der Tatsache, dass wir ganz gezielt manipulieren können,
was wir instanzieren wollen und was wir nicht instanzieren wollen.
Nächstes großes Thema ist die Suchraummanipulation. Also das Thema
Muss ich wirklich immer den gesamten Suchraum abarbeiten oder kann ich
mir die Sache nicht ein bisschen einfacher machen?
Also hier geht es darum, Teile des Suchraumes abzuschneiden,
wenn es denn aus irgendwelchen Gründen nicht erwünscht ist,
dass man sie betritt. Und der hauptsächliche Grund dafür ist,
in diesem Teil des Suchraumes lauert ein Terminierungsproblem.
Dann muss ich den gesamten Suchraum vermeiden, zu betreten.
Es gibt auch noch die zweite Variante und das werden wir unterscheiden.
Das ist dann roter und grüner Cut.
Es gibt auch die zweite Variante, die nur dazu dient,
die Effizienz zu verbessern. Wenn ich also weiß,
in dem Teil des Suchraumes kann kein Ergebnis nicht mehr befinden,
dann muss ich da gar nicht
erst einsteigen
Ja, durch das spezielle Nullstätigeprädikat Cut erzeuge ich immer ein eindeutiges Berechnungsergebnis.
Und schlimmer noch, ich erzeuge ein eindeutiges Berechnungsergebnis.
Das müssen wir immer im Kopf haben. Der Preis für
das Setzen des Cuts ist in den meisten Fällen,
das möglicherweise gewünschte Mehrfachergebnisse dabei auch verschwinden.
Also das kommt nicht ganz ohne unerwünschte Nebeneffekte daher.
Die Semantik ist ein bisschen kompliziert und die muss man
sich sehr auf der Zunge zergehen lassen.
Der Cut, wenn er denn verarbeitet wird,
er gelingt immer, der Cut ist immer tru,
egal was passiert,
der kann nie scheitern
Wenn denn dieses Ausrufezeichen irgendwo drinsteht im Körper einer Klausel,
dann legt der Cut die Suche auf die Auswahl fest,
die seit der Unifikation des Klauselkopfes entstanden ist.
Also es trägt irgendwo im Klauselkörper tritt der Cut auf,
wenn er prozessiert wird,
dann wird die aktuelle Alternative als die einzige betrachtet.
Und das hat zwei Konsequenzen und die muss man beide beachten,
sonst hat man das nicht.
Also er schneidet alle Suchpfade ab, die sich aus alternativen
Klauseln ergeben würden. Also es kann nicht mehr sein,
dass eine der doch darauf folgenden Klauseln betreten wird,
die sind nicht mehr existent
Für das Programm. Und Alternative Wertebindungen,
die möglicherweise noch möglich wären, also links von dem Cut,
wenn dort noch Entscheidungspunkte offen sind, die verschwinden ebenfalls.
Das sind also die beiden Wirkungen.
Alle alternativen Klauseln sind weg und alle alternativen Wertebindungen,
die in der bisherigen Verarbeitung aufgebaut werden, die sind ebenfalls verschwunden.
Und damit hat man die zwei Wirkungen einerseits,
das Backtracking in Teilziele, die links vom Cut stehen,
das geht nicht mehr.
Und andererseits klauseln die in der Datenbank auf den Cut folgen,
die sind unsichtbar geworden,
die existieren für das Produktsystem
nicht mehr
Dynamisch. Natürlich sind sie noch im Speicher,
aber für die aktuelle Suche sind sie nicht mehr existent.
So, und das müssen wir uns an dem Beispiel versuchen,
deutlich zu machen, ich habe es ein anstraktes Beispiel genommen,
weil man verschiedene Effekte daran zeigen möchte. Wir haben also hier ein Prädikat,
was zwei alternative Klauseln hat, das heißt hier in dem Suchraum,
und das ist uninstanziert aufgerufen,
habe ich hier oben den großen Branch zwischen dem Anwenden der ersten Klausel,
dieser hier und dem Anwenden der zweiten Klausel,
dann wird hier per Unifikation eine Variablenbindung vorgenommen und dann geht es hier in den Körper der Klausel,
das ist dieses B zweistellig und c einstellig,
in diesem Falle,
und das ist das D einstellig im zweiten Fall
Da muss ich den Erd, das erste Peilziel abarbeiten.
Das erste Teilziel ist, führt auf diesen Fakt hier.
Und da muss ich noch das zweite Teilziel abarbeiten.
Das ist der Aufruf hier unten. Das Gleiche hier in
diesem Zweig des Suchraumes und hier unten haben wir den Aufruf von D
von X und D von X führt auch nur auf diese Alternative von Fakten.
Also diese Zweifachverzweigung mit den beiden Alternativen, rechts und links.
Also wenn ich das Ganze unitstanziert aufrufe, wird hier der Suchraum ja wieder immer erst in die Tiefe expandiert. Ich nehme diese Klausel,
ich komme zum Körper der Klausel, ich nehme das erste Teilziel,
das erste Teilziel ist erfolgreich mit der vaiablen Bindung X1,
ist gleich A und Y1, ist gleich B,
dann muss ich das zweite Teilziel in der Klausel noch aufrufen und das ist eben Y und Y ist gerade eben an B gebunden worden,
das heißt also der Aufruf ist jetzt C von B,
der ist einmal mit diesem Fakt hier erfolgreich und mit diesem Fakt scheitert er.
Hier in der Alternative habe ich eben den Aufruf B von
BC, das ist das erste Teilziel der Klausel
Das ist erfolgreich mit dieser vajablen Bindung. Und damit habe
ich Y wieder gebunden. Ich gebe mit dieser vajablen Bindung
für Y in das zweite Teilziel der Klausel.
Damit scheitert die Unifikation mit C von W, mit diesem Fakt hier.
Oder es ist erfolgreich mit dem zweiten Fakt für C.
Und analog dazu in dem rechten Zweig der Suche.
Jetzt modifiziere ich das Programm ganz minimal, indem ich hier einen Cut einbaue.
Und das heißt, an dieser Stelle der Suche wird jetzt hier der Cut prozessiert.
Also es ist die Nein, der taucht hier auf,
prozessiert wird er hier. Ich arbeite ganz normal ab,
ich habe das erste Teilziel, das erste Teilziel oder der Körper,
der Klausel wird aufgerufen, unter dieser vajablen Bindung,
ich muss das erste Teilziel abarbeiten, das passiert auch,
es wird X und Y an einen Wert gebunden und dann wird der Cut prozessiert.
Das macht keine Probleme, der gelingt immer,
aber es werden alle Alternativen, die links vom Cut stehen, weggestrichen.
Das heißt, dieser Aufruf hier, und das ist eine
andere Instanziierungsvariante für dieses Teilziel, das verschwendet.
Also Alternativen, alternative Wertebindung, die Links vom Cut noch existieren,
die verschwinden
Und gleichzeitig verschwinden die alternativen Klauseln, die noch in der Datenbank sind.
Also ich bin gerade beim Abarbeiten dieser Klausel.
Also verschwindet die zweite Klausel für A von X,
die ich hier habe, aus dem Suchproblem.
Das sind die beiden Bereiche aus der Suche, die einfach weiterhin ignoriert werden.
Das heißt, das System kommt jetzt tatsächlich dazu,
dass es hier ein erfolgreiches Ergebnis liefert.
Und das Ergebnis wird auch ausgegeben. Also die vaiablen Bindung,
ach nein, X wird nur nach draußen gegeben,
X ist dann also gleich A.
Also A von A wäre eines Ergebnis, was sich ergibt.
Hier kriege ich kein weiteres Ergebnis hinzu. Es könnte sein,
dass hier unten weitere Ergebnisse produziert werden. Also alternative Werdebindung,
die rechts vom Cut auftreten, die werden nicht weggeschnitten.
Die bleiben immer noch drin. Der Cut schneidet nur links vom,
also das, was bisher passiert ist, da werden die Alternativen weggeschnitten.
Also das, was ich vorhin gesagt habe, das wird immer eindeutig gemacht.
Das ist eine wichtige Verwendungsweise des Cuts. Aber es erklärt nicht
die gesamte Mantik. Ich kann auch mit einem Prädikat,
was einen Cut enthält, möglicherweise mehrfach Ergebnisse produzieren.
Typischerweise wird es dafür aber nicht verwendet.
Typischerweise wird es so verwendet, dass man eine Entscheidung
eindeutig macht
Von der Pragmatik her hatte ich schon angedeutet,
gibt es zwei Sorten von Cuts. Das eine heißt Greencut,
weil er unproblematisch ist, das andere heißt Redcut,
weil er mit Vorsicht zu genießen ist.
Greencuts sind die, die es mir erlauben,
in eindeutigen Countexten deterministisch zu programmieren.
Das führt zu einer Effizienzsteigerung, lässt die Prädikat-Semantik aber unbeeindruckt.
Der Redcut heißt immer, eigentlich mehrdeutige Relation wird zwangsweise funktional gemacht.
Und das ist immer mit einer Änderung der Programmsemantik verbunden.
Und da muss man wissen, was man eigentlich tut,
ob man diese Änderung der Semantik mittragen will oder nicht
Und das muss man lernen, die als eine von dem anderen zu unterscheiden.
Also hier ein Beispiel für einen grünen Cut. Wir wollen
ein Prädikat definieren, was uns das Maximum zweier Zahlen gibt.
Also X und Y sind die Input, Vablen.
Und ich kriege hier auf der dritten Argumentstelle das Maximum der beiden heraus.
Die Definition ist ziemlich simpel. Es ist das erste Argument,
wenn denn X größer als Y ist.
Es ist das zweite Argument, wenn ein Y größer oder
gleich X ist. Ich weiß jetzt dadurch,
dass diese Bedingungen hier gegenseitig sich ausschließen, weiß ich,
wenn das scheitert, nein,
wenn das erfolgreich ist,
das erste,
dann muss das zweite zwangsläufig scheitern
Ja. Das heißt, jetzt kann ich hingehen und kann
hier einen Cut setzen. Wenn denn, ich weiß,
dass das Ganze erfolgreich war, durch diesen Test bin ich bereits durch.
Dann kann ich sagen, schneide alle Alternativen ab und
dann verschwindet diese Klausel, die wird nicht mehr angefasst.
Denn das Ergebnis steht bereits fest. Ich weiß, hier
werde ich kein zweites Maximum bekommen. Das einzige Maximum,
was es gibt, habe ich bereits gesehen.
Da kann ich also mit großer Sicherheit sagen,
hier musst du nicht mehr reingehen, das musst du dich nicht mehr angucken.
Da gibt es kein weiteres Ergebnis. Deshalb ist das also ein Greencut.
Nun könnten wir Waaghals wackhalsig werden und sagen, ja,
Moment, brauche ich dann hier diesen Guard überhaupt noch hier hinten?
Ja, also ich lasse ihn mal weg. Ich sage nur,
ja, wenn, denn X größer sei es Y,
dann ist X das gesuchte Maximum.
Das ist die Chor-Referenz zwischen Argumentstelle 1 und 3. Und danach mache ich einen Cut.
Das heißt, hier komme ich dann nur hin,
wenn ich jetzt tatsächlich, wenn dieser Test gescheitert ist.
So, und das können wir uns im Test angucken.
Also das Maximum von 3 und 2 ist also 3,
das ist sehr schön, das Maximum von 3 und 2,
3 instanziert, getestet ist auch wahr.
Und wenn wir jetzt aber das Maximum 3, 2,
2 eingeben, dann kriegen wir plötzlich Truhe raus
Woran liegt es? Wir müssen noch mal gucken.
Dieser Aufruf hier scheitert jetzt bereits, weil die Co-Referenz zwischen
dem ersten und der dritten Argumentstelle nicht mehr erfüllt ist.
Das heißt, in diesem Aufruf Variante kommt das Ding
überhaupt nicht dazu, diesen Cut jemals zu prozessieren.
Der scheitert bereits in der Unifikation des Klauselkopfes.
Hier ist er bereits ausgestiegen. Und deshalb kommt er also
zu der zweiten Klausel und die zweite Klausel sagt eben zufälliger,
weil diese Co-Referenz jetzt zufälligerweise stimmt, sagt sie true,
obwohl es nicht wahr ist
Hier haben wir also jetzt einen Fehler eingebaut.
Wir müssen entweder das korrigieren, was wir hier gemacht haben.
Oder wir müssten das Prädikatschema korrigieren und sagen,
wir dürfen das dritte Argument nicht instanzieren.
Und das heißt das Minus hier. Das, was wir
hier gemacht haben, ist hochgradig äquivalent zum FNLs.
Da kommen wir noch dazu. Das FNLs hat eine sehr,
sehr starke Ähnlichkeit mit dem Trick, den wir hier angewendet haben.
Und ist ein sicheres Zeichen dafür, dass das if
Van Els nicht logisch sanktioniert ist.
Das wird eine der wesentlichen Schlussfolgerungen sein,
die wir demnächst eher ziehen können
Was man machen sollte, ist aber,
dass es ein Redcut, die Implementation ist schlecht.
Wir haben die bessere Implementation gesehen. Das war auf der Folge
davor. Wenn wir das so machen, sind wir auf
der sicheren Seite. Also der Guard ist immer noch erforderlich,
um tatsächlich dieses Instanzierungsschema vollständig zu reflektieren.
Das Tückische an dieser Variante ist hier tatsächlich,
dass das Problem eigentlich in einem Fallauftritt, den wir als harmlos bezeichnen würden.
Bisher haben wir die Probleme immer dann gehabt, wenn wir
uninstanzierte Variablen verwendet haben. Wenn wir das Ganze unterspezifiziert aufgerufen haben,
hier kommt das Problem jetzt plötzlich in einem vollständig entspezifizierten Aufruf hoch.
Und das liegt daran, dass wir mit dem Cut
die sichere Basis der Logik verlassen. Was das bedeutet,
kommt gleich noch. Wir können den Redcut auch verwenden,
wenn wir schon wissen, dass es ein garantiert eindeutiges Ergebnis gibt.
Also beispielsweise bei der Suche nach der Mutter können wir sagen,
wenn wir die erste Mutter gefunden haben,
dann müssen wir nicht nach der zweiten Mutter suchen.
Also in diesem ist Mutter Prädikat,
können wir also an der Stelle suchen,
nachdem wir die erste Mutter gefunden haben, können wir die Suche abbrechen.
Eine zweite Suche nach weiteren Müttern wird aussichtslos sein,
wenn denn die Datenbank in Ordnung ist.
Natürlich unter der Voraussetzung immer. Wir müssen immer wissen,
dass damit der gesamte relationale Charakter verloren geht.
Wenn es jetzt, wenn wir das gleiche Prädikat benutzen wollen,
um alle Mütter aufzuzählen, das funktioniert nicht mehr.
Nach der ersten Mutter ist Feierabend. Das können wir hier sehen.
Wir nehmen das Mutter von Prädikat und machen den gleichen Trick.
Und durch den Cut hier stellen wir sicher,
dass nach dem ersten Suchergebnis terminiert wird.
Also wir haben die folgenden Fakten. Susi ist die Mutter
von Hans und sie ist auch die Mutter von Anna.
Und wenn wir jetzt fragen, von wem ist Susi die Mutter,
Und dann kriegen wir eben das erste Ergebnis Hans.
Danach ist der Cut prozessiert worden und dann ist Feierabend.
Dann werden alle Alternativen Links vom Cut weggeschnitten. Und die
Alternative steckt hier in diesem Teilziel. Da gab es noch
einen zweiten Fakt, den sehen wir leider nicht mehr.
Wir sind bei dem ersten bereits erfolgreich gewesen, das Ergebnis kriegen wir doch.
Danach wird der Cut produziert, danach ist Elternteil auf die
vajablen Bindung, die wir hier hatten, festgelegt.
Und das war die mit Hans. Eine weitere gibt es
nicht mehr.
Der Rest ist weggeschnitten
Also hier an der Stelle muss man sich überlegen,
welche Aufrufvarianten will man tatsächlich verwenden, wenn es nur darum geht,
gib mir die Mutter zu einem gegebenen Kind,
dann ist der Cut okay.
Aber wenn ich es in einer anderen Richtung auch verwenden will,
dann schneide ich einen Teil der Ergebnisse weg.
Das ist der Preis, den man mit dem Cut immer bezahlt,
darüber muss man sich im Klaren sein,
dass das nicht ganz ohne unerwünschte Nebenwirkungen kommt.
Die Stelle, wo es systematisch verwendet wird,
ist das sogenannte Default schließen.
So faul schließen heißt immer, ich habe eine Regel,
die gilt im allgemeinen Fall.
Aber es gibt zu dieser Regel
zahlreiche Ausnahmen
Ja, also was weiß ich, ein Stuhl hat vier Beine,
aber es könnte natürlich genauso sein, dass es ein Stuhl
mit drei Beinen gibt oder mit sechs Beinen oder was auch immer.
Hier habe ich das mal für Tiere gemacht. Ich habe den Normalfall genommen,
dass Tiere haben sechs Beine, das habe ich nicht ganz zufällig gewählt,
weil das die Insekten sind und im Reich der Tiere sind Insekten die häufigsten Spezies,
die wir haben.
Also Standard ist irgendetwas, was nicht weiter spezifiziert ist,
hat sechs Beine. Und dann habe ich zahlreiche Ausnahmen dazu,
also Säugetiere beispielsweise haben nur vier Beine.
Man bemerke, dass auch der Mensch im Bereich der Säugetiere
wieder eine Ausnahme darstellt, im Gegensatz zu Hund,
Katze und Elefant
Lorche haben auch, typischerweise vier Beine, Spinnen haben acht Beine,
Vögel, zwei Beine und so weiter. Gut,
wenn wir das so hinschreiben,
dann haben wir natürlich die üblichen Konsequenzen davon,
wenn wir jetzt fragen, wie viele Beine haben Spinnen,
dann kriegen wir eben beide Antworten, ja acht,
aber möglicherweise auch sechs.
Irgendwann komme ich ja in der Alternativensuche hier unten auf den
Defaultfall zurück und der Leve hat mir auch ein Ergebnis.
Das ist also unerwünscht, dieses Verhalten hier.
Dieses Verhalten ist erwünscht, welche Tiere haben denn vier Beine?
Ja, das sind die Lorche und die Säugetiere.
Das ist ein vernünftiges Ergebnis, das möchte ich
so haben
Wenn ich jetzt einen Cut setze, das ist das Wesentliche, mache ich beides kaputt.
Also ich mache, oder nicht kaputt, ich mache,
sorge dafür, dass hier das Mehrfachergebnis verschwindet und hier ebenfalls.
Hier möchte ich es, hier möchte ich es nicht.
Und das ist genau die Zwickmühle, in der man sich immer befindet.
Also wir machen es so, dass wir sagen, ja,
wenn wir denn schon von zwei Beinern und Vögeln gesprochen haben,
dann können wir den Cut setzen und dann verschwinden alle Alternativen,
inklusive dieses Defaults daraus. Und wenn wir jetzt fragen,
hat eine Spinne, wie viele Beine hat eine Spinne,
dann gehen wir eben hier durch,
bis wir bei dem ersten erfolgreichen Ding landen,
Dann wird der Cut prozessiert und damit verschwinden die anderen vier Alternativen aus der Betrachtung.
Und es werden keine weiteren Ergebnisse erzeugt. Und wenn wir
jetzt aber fragen, welche Tiere haben vier Beine,
dann gehen wir eben durch, bis wir das ersten,
den ersten Eintracht, mit der wir finden.
Danach sorgt der Cut dafür, dass alle anderen verschwinden.
Und damit verschwindet auch das zweite Ergebnis,
dass ja auch die Säugetiere noch vier Beine haben.
Also, was wir erreicht haben,
ist Funktionalität in beiden Argumenten wollen wir nicht.
Eine Instanzierungsvariante geht uns dadurch verloren. Und was auch wesentlich ist,
dass die Reihenfolge der Klauseln jetzt signifikant für das Ergebnis wird.
Wenn wir die Klauseln jetzt umsortieren,
kriegen wir
völlig andere Ergebnisse heraus
Also offensichtlich haben wir hier hart in den logischen Ableitungsmechanismus der Maschine eingegriffen und das hat gravierende Konsequenzen für das,
was wir noch als Ergebnisse zu sehen bekommen.
Das kann man dadurch beschreiben, das,
was wir hier eingeführt haben, nicht monotones Schließen ist,
um zu verstehen, was nicht monotonisch Schließen heißt und Default schließen,
ist ein Spezialfall, das ist, glaube ich,
der einfachste Spezialfall des nicht monotonen Schließens, um zu verstehen,
was nicht monotonisch Schließen ist, müssen wir wissen,
was monotonisch Schließen ist.
Monotonie in der Logik heißt immer,
wenn ich neue Axiome hinzunehme,
dann wird die Anzahl der ableitbaren Theorie
zumindest nicht kleiner
Sie wächst monoton, sie kann auch konstant bleiben, aber zumindest wird sie nicht kleiner.
Heißt hier, ich habe eine Aktion in Menge 1,
die ist Teilmenge von einer Aktion Menge 2.
Und ich kann aus A1 eine Menge von Theorien ableiten und
aus A2 eine Menge von Theorien ableiten, dann weiß ich,
dass die Theorienmenge, die aus A1 ableitbar ist,
kleiner oder gleich der Theoriemenge aus A2 ist.
Oder mit anderen Worten, ich erweitere die Theorie,
ich mache sie größer, dann gehen keine Schlussfolgerungen verloren.
Anders bei der Nichtenmonotonie,
wenn ich jetzt neue Aktionen hinzunehme,
führt das dazu,
dass alte Theorieme verloren gehen
Also hier können jetzt alte Schlussfolgerungen, die vorher noch möglich waren,
sind in der erweiterten Theorie dann plötzlich nicht mehr möglich.
Und das gucken wir uns am Beispiel an. Wir haben also hier
eine Theorie unter Monotonie. Wir gucken uns erst den Fall,
den wir bisher immer betrachtet haben, an, ja.
Monotonische Schlussfolger. Wir haben eine Axiomenmenge, die enthält nur dieses eine Aktion.
Irgendetwas hat sechs Beine. Und wenn wir jetzt fragen,
wie viele Beine hat ein Regenwurm, dann kriegen wir eben
die Antwort sechs. Wenn wir jetzt die Theorie,
die Aktionenmenge erweitern durch den zusätzlichen Fakt,
ja ein Regenwurm hat Nullbeine Und wir fragen jetzt wieder,
wie viele Beine hat ein Regenwurm, dann kriegen wir zwei Antworten.
Und die alte Antwort, die wir hier hatten,
ist wieder mit dabei. Es ist uns nichts verloren gegangen
. Das ist nicht monoton, das ist monotonisch ließen.
Hier, wir haben die alte Antwort B gleich 6
und wir kriegen eine neue Antwort hinzu.
Das heißt also, die Menge der ableitbaren Theorieme wächst,
weil wir die Menge der Axiom erweitert haben.
Jetzt der umgekehrte Fall nicht Monotonie. Wir haben wieder dieses
einfache Axiomsystem mit dem einen Fakt drin und wir kriegen das,
was wir schon eben gesehen haben, ein Regenwurm hat sechs Beine.
Jetzt nehmen wir, wie wir das vorher bei dem Defaultschließen
ja gesehen haben, hier die Ausnahme,
spezifizieren wir mit diesem Cut
Und der Cut bewirkt jetzt, dass der default nicht mehr
sichtbar ist, wenn denn das hier erfolgreich war,
die Unifikation mit hat Beine Regenwurm.
Wenn ich jetzt also nach Hatbein Regenwurm frage,
dann kriege ich hier das Ergebnis null,
dann wird der Cut verarbeitet und dadurch verschwindet der Default,
die Default-Klausel und es gibt kein weiteres Ergebnis.
Ich habe also die Aktionenmenge größer gemacht und gleichzeitig ist mir
ein altes Theorien, was ich ableiten konnte, verloren gegangen.
Das ist nicht Monotonie. Und eine der wesentlichen Erkenntnisse wird sein,
dass all das,
was wir unter Negation oder If-Van Els und so weiter verstehen,
dass das
nicht monotonisch schließen ist
Das gucken wir uns aber dann nächste Woche an.
Hier nur zur Abregrundung, das haben sie alle schon gesehen.
Natürlich kann ich die Datenbanken manipulieren. Natürlich kann ich dadurch
den Zustand des Prologsystems verändern. Deshalb muss man damit kontrolliert und bewusst umgehen,
also konsultieren einer Datei, haben wir alle schon oft gemacht.
Es hört, es hört A, SOZ und so weiter,
das haben wir schon in der allerersten Übung uns angeguckt.
Wir können natürlich auch Klauseln aus der Datenbank entfernen.
Entweder indem wir es ganz gezielt machen,
indem wir dem Funktor und die Stelligkeit angeben,
dann wird also eine ganze Prädikatsdefinition mit diesem Funktor in dieser Stelligkeit beseitigt.
Wir können hier ein Pattern angeben und dann wird die erste Klausel,
auf die das Pattern passt,
entfernt oder wir nehmen Rack Tracked All und alles,
was auf dieses Pattern passt, wo der Kopf hiermit unifiziert,
das muss man genau sagen.
Wo der Kopf mit dem hier,
mit der hier angegebenen Struktur unifiziert, alle Klauseln werden hier entfernt,
hier ist es nur die erste, die entfernt wird.
Was wir auch brauchen werden, ist,
Suche von Klauseln in der Datenbank, wenn wir Metaprogrammierung betreiben wollen,
dann wollen wir auch dem Prologsystem aus der Hand nehmen,
wie es die Dinge in der Datenbank sucht.
Denn die Suche ist immer saft, damit verbunden,
ich finde eine passende Klausel und ich unifiziere sofort den Kopf der Klausel.
Das ist zu spät. Also wir müssen einen Klausel finden
können, ohne dass wir mit dem Kopf unifizieren.
Und dazu werden wir dieses Prikat verwenden.
Das erlaubt es uns, zu sagen,
welche Klausel suchen wir
Und wir können dann die Klausel kopieren,
können auf der Kopie arbeiten und lassen die Datenbank unangetastet.
Ja, wir kriegen also auch den Körper dazu geliefert.
Wir können uns überlegen, was die normale Suche macht,
die sofort, wenn der Kopf unifiziert hat,
wird sofort die vajablen Bindung auf den Körper der Klausel übertragen.
Hier kriegen wir einfach nur den Körper der Klausel übertragen.
Und wir können uns überlegen, ob wir es möchten,
dass der Körper installiert wird oder nicht.
Wenn wir das nicht wollen, können wir es weglassen.
Also das wird ein ganz,
ganz wesentliches Element der Metaprogrammierung werden,
wo wir dem System auch noch
die Suche aus der Hand nehmen
Wir nehmen ihm die Suche aus der Hand, wir nehmen ihm
die Unifizierung aus der Hand und dann modifizieren wir die Dinger.
Dann nehmen wir eine neue Suchstrategie. Das wird das Ziel
sein, das Prolog-System mit einer breiten Suche zu implementieren.
Weg von der klassischen Tiefensuche, die standardmäßig mitgeliefert wird,
hin zu einer Suchstrategie, die uns besser gefällt.
Dass wir eine Suchstrategie realisieren, die es vermeidet,
dass das Prolog-System in Zyklen laufen kann.
Das wird die Zielstellung sein. Und dazu müssen wir
dem Ding die Suche selber aus der Hand nehmen.
Das ist das Mittel dafür
Gut, wir können das,
das gibt es auch noch wieder Varianten davon,
wo wir die Ente Klausel eines Prädikats,
also was weiß ich, die dritte Klausel des Prädikats,
was weiß ich, Mutter oder was wir da eben hatten,
anfassen kann und so weiter und so fort.
Aber für das muss man erst mal wissen, wo es steht,
hier, da kann man das tatsächlich über die Hash,
das Session-System, das System des Produktsystems, den Zugriff zu beliebigen Klauseln machen.
Letzte Amtshandlung für heute, dann sind wir durch,
sind die globalen Variablen, hatten wir auch schon gesehen,
ich bin also in der Lage, mit einem Namen,
hier in diesem Falle Accounter, bestimmte vajablen Bindung zu verbinden.
Und die ist dann global durch das ganze System sichtbar.
Und dieses Mittel ist eben dieses Fleck-Prädikat, typische Verwendungsweise,
es geht auch noch ein bisschen mehr. Für nicht-numerische Atome,
aber die typische Verwendungsweise ist beispielsweise für so einen Zähler. Dass ich,
wenn nicht, so haben wir es ja verwendet,
wenn ich einem Verzeichnis einer Datei einen eindeutigen Identifikator geben will,
dann und wissen will, was ist der letzte vergebene Identifikator,
dann kann ich beispielsweise das auf seiner globalen Variablen speichern,
die ich eben erstmal mit im Initialzustand,
wenn sie vorher keinen Wert hatte, ist dieses N gleich Null.
Und wenn ich hier das N plus 1 eintrage,
wird bei jedem Aufruf um eins erhöht und dann kann ich
das als globalen Zähler verwenden
Gut, das ist der Punkt, bis zu dem ich heute kommen wollte.
Wir gucken uns dann das nächste Mal Prädikate höherer Ordnung an.
Und da ist dann tatsächlich das Ziel, wo wir hinkommen wollen.
Und das sollten wir vor Weihnachten noch erreichen,
ist die Metaprogrammierung. Das ist der Punkt,
wo wir dem Prologsystem die Kontrolle im Innen homöopathischen Dosen,
aber zunehmend schärfer aus der Hand nehmen und sagen,
das wollen wir bitte selber entscheiden, wie die Verarbeitung vonstatten gehen wird.
Okay, so viel zur als Vorankündigung.
Schönen Dank für die Aufmerksamkeit, schönes Wochenende und bis
nächsten Freitag