So, einen schönen guten Tag auf ein neues.
Wir hatten uns ein zusätzliches Gesundheitskonstrukt angeguckt, die Liste,
um damit symbolische Verarbeitung zu betreiben,
weil Listen als dynamische rekursive Datenstrukturen universell verwendbar sind und andererseits
auch sich sehr flexibel an verschiedene Ressourcenbedürfnisse anpassen lassen.
Das ist das Schöne an verketteten Listen, dass man sie
immer beliebig erweitern kann. Wir hatten uns verschiedene Prädikatsdefinitionen angeguckt
und Bei der Gelegenheit muss sich feststellen, dass das,
was hier auf den Folien steht, leider nicht mehr aktuell ist.
Wir gucken uns das mal genau an. SWI hat wieder
mal das Interface verändert, also das Interface zum Nutzer.
Unendliche Datenstrukturen werden jetzt anders behandelt, als sie früher,
nein, falsch. Sie werden nicht anders behandelt,
als sie früher behandelt worden sind, sondern sie werden anders dargestellt.
Wir schauen uns das einfach mal am Beispiel an. Wenn wir so etwas machen,
wie wir unifizieren die Variable X mit einer Struktur,
die X enthält, das hatten wir ja gesehen,
das ist wie bei den PANO-Zahlen, dann entsteht eine eine unendliche Struktur.
Etwas, was die mathematische Logik verbietet,
was aber in einem solchen System aus Effizienzgründen beziehungsweise aus Gründen,
weil man damit nette Sachen machen kann, nicht ausgeschlossen wird.
Dann sieht es auf der Oberfläche jetzt so aus,
als sei nichts passiert. Genau das, was man erwarten würde,
die Struktur A von X ist an die Vagable X gebunden worden.
Das ist also wenig spektakulär, was da passiert ist.
Jetzt aber können wir mal gucken, was in der Struktur tatsächlich passiert ist.
Wir können also uns die Teile davon ausgeben lassen,
dass ich also sage, dann sage mir doch bitte mal,
was ist Y eigentlich, wenn
Und dass das Innere dieser Struktur ist,
und das Spiel kann ich weitertreiben, indem ich sage,
dann sage mir doch bitte mal, was Z ist,
beim Z, das Innere dieser Struktur Y ist.
Und das können wir dann natürlich nach Belieben fortsetzen und
dann sag mir doch bitte mal, was A,
das Innere dieser Struktur Z ist.
Und das sieht jetzt auf der Oberfläche so aus,
dass wir dort tatsächlich die Informationen bekommen, die man eigentlich erwarten sollte.
Insofern ist das mal eine echte Verbesserung des Interfaces,
nachdem es zwischenzeitlich mal verschlimmbassert worden ist
Wir sehen jetzt hier, dass X gleich Y ist,
obwohl eben das Y das Innere dieser X-Struktur sein soll.
Es ist gleich dem Äußeren der A-Struktur, sorry. Wir sehen,
dass Z das Innere der Y-Struktur ist und wir sehen gleichzeitig
das Z, das Innere der Inneren der X-Struktur ist.
Und wir sehen hier hinten, dass A auch wieder eine Struktur ist,
die beliebig in die Tiefe geht, deren Inneres wir wieder abfragen könnten.
Auf die Art und Weise kann man jetzt durch Probebohrungen an der Struktur tatsächlich feststellen,
Ja, was ist im Inneren der Struktur eigentlich enthalten?
Das können wir auf ganz unterschiedliche Art und Weisen machen.
Ich hoffe nicht, dass ich jetzt ein Experiment mache.
Weiß jemand, wie die Argumentreihenfolge von MF ist?
Ich weiß es immer nicht. In diesem Falle hilft die Hilfe manchmal.
Achso, ja, nein, das ist natürlich über Listen.
Da müssen wir erstmal uns Listen angucken.
Wir können also das gleiche Spiel auch mit Listen machen,
indem wir sagen, X soll eine Liste sein,
die ein Element enthält und dann wieder die Restliste X. Oh,
da ist was verloren gegangen.
Auch hier sehen wir jetzt wieder, dass oberflächlich sich nichts ändert.
X hat als Wert jetzt eine Liste,
die A enthält und danach gefolgt wird von einer Restliste,
die wieder X ist. Und auch hier können wir jetzt
verschiedene Tricks anwenden, um in die Tiefe zu bohren.
Also jetzt, wenn wir das tatsächlich mal an.
Also wir wollen das erste Element von X wissen und das soll dann
A heißen und wir sollen das Ach nein, so natürlich nicht.
Das zweite, hallo. Wollen das zweite Element von X-Wissen,
und das soll ein W heißen, und na,
weil es so schön ist,
machen wir auch noch das dritte Element von X.
Und das soll ein C heißen.
Was habe ich gemacht? Ach ja, da fehlt
eine Eins
Gut, und da sehen wir jetzt eben, dass A,
gleich B, B, gleich C, C gleich A ist.
Das heißt also, alle A, B und C,
Bajablen haben, es kackte den gleichen Wert.
Sie sind mit Co-Referenz miteinander verbunden und sie haben den Wert A. Das heißt,
wie tief auch immer ich in die Liste hineingehe,
ich kriege immer wieder das gleiche Element.
Und zwar das identisch gleiche Element. Also auch wenn dort eine Vagable Stünde wäre,
die immer per Co-Referenz mit den anderen Vagablen verknüpft. Gut,
also da hat sich die Oberfläche des Prolog-Systems ein bisschen geändert,
Auf die Art und Weise wird jetzt sehr schön hier die Ko-Referenz dargestellt,
die man vorher so nicht bekommen hat.
Das halte ich also für einen Vorteil. Der Nachteil ist,
ich kriege es erst, wenn ich tatsächlich mal ein bisschen nachbohre und schaue,
was im Inneren dieser Struktur eigentlich enthalten ist.
Natürlich ist das, was hier steht, so mantisch völlig korrekt.
Die Zyklizität dieser Liste ist auch hier schon zum Ausdruck gekommen.
Und natürlich kann ich so etwas machen,
indem ich jetzt beliebig lange Listen aufbaue,
indem ich eben mehrere Elemente dort drin habe,
Und dann das Ganze zyklisch mache, das war die Frage,
die Sie letzte Woche gestellt haben.
Und dann habe ich eben eine zyklische Liste, die ABC enthält.
Und wenn ich jetzt in dieser Liste zyklisch herumwandere,
indem ich immer sage, gib mir das nächste Element.
Also ich kann jetzt hier beispielsweise sagen, gib mir das
25. Element dieser Liste. Und nenne SE. Und dann
sieht man eben einen Überlauf, kann nicht mehr passieren.
Das Ding geht eben munter in der Liste herum und sagt
mir, das 25. Element davon ist ein A.
Wir können dann gucken, dann müsste das 26. Element
ein B sein.
Mal sehen,
ob das stimmt
Genau, und dann müsste das 28. Element wieder ein A sein.
Das stimmt auch. Auf die Art und Weise kann man
zyklische Listen aufbauen, indem man unendlich lange im Kreis wandern kann.
Die einzige Frage ist nur, wie stellt man ein solches zyklisches Konstrukt tatsächlich dar?
Da sind also früher irgendwelche Varianten erfunden worden mit zwei Sternen drin.
So hatte ich es vorher hier Woche noch auf der Folie.
In der Zwischenzeit zieht die Oberfläche ein bisschen anders aus.
Gut, und das habe ich hier dann mal nein,
wie ich sehe, noch nicht ganz konsequent eingebaut.
Ja, ach doch, ja, doch,
das ist der aktuelle Zustand
Wir erzeugen jetzt hier eine Liste,
wo das erste Element die Liste selbst wieder ist,
wo also in die Tiefe geschachtelt wird.
Und dann können wir eben sagen, gib mir das erste
Element dieser Liste und schreibe mir das Ding aus.
Und dann kriegen wir eben hier eine, bei dem Reitbefehl,
eine Darstellung, die sagt, ich kann es leider nicht ausdrucken.
Es ist ein Therm, der unendlich tief geschachtelt ist.
Ich müsste unendlich viele Klammern aufmachen und unendlich viele Klammern
wieder zumachen, das kann ich nicht.
Und stattdessen wird dieses ausgegeben. Das ist aber
die Antwort auf diesen Druckbefehl hier
Wenn ich einfach nur die Variablenbindung wissen will, naja,
dann sagt er mir genau das, was die Unifikation rausgekommen ist,
was nämlich in der Definition von First drinsteckt,
das erste Element der Liste ist, na,
das ist nicht die Definition.
Hier ist die Definition, das erste Element der Ergebnisliste ist das hier vorgegebene Element.
Und genau das kriege ich dann heraus, als Ergebnis,
das ist nicht überraschend, das erste Element ist gleich der Liste selbst.
Das ist das, was ich reingesteckt habe, per Definition
kriege ich als Ergebnis wieder ausgegeben. Und hier ist nochmal das,
was ich ihnen eben vorgeführt habe, wir kreieren eine zyklische Liste,
in diesem Fall nicht in die Länge, unendlich,
sondern hier die Tiefe unendlich.
Und dann können wir eben gucken, gib mir das erste Element,
gib mir das zweittief eingeschachtelte Element, das auf der dritten Schachtelungsebene und so weiter.
Und wir sehen wieder auf der ersten Schachtelungsebene ist gleich der zweiten,
ob der zweiten ist gleich der dritten und auf der dritten
finden wir wieder eine Struktur, die in sich selbst eingebettet ist.
Gut, und das ist genau die Information, die wir rausbekommen wollten.
Also das hat sich ein bisschen die Oberfläche geändert,
aber das ist nichts Dramatisches. Es ist ja ein ernsthaftes Problem,
wie will ich also etwas darstellen,
was ich eigentlich nicht darstellen kann,
nämlich unendlich große Strukturen
Da muss man sich was einfallen lassen. Gut,
wir hatten uns das Member-Prädikat angeguckt, also ein Test darauf,
ob ich ein Element in einer Liste finden kann.
Das ist die übliche Verwendungsweise, die wir etwa in der funktionalen Programmierung
auch hier haben. Hier haben wir jetzt die Möglichkeit,
auch noch das Ganze in anderen Instanzierungsvarianten aufzurufen. Also beispielsweise,
gib mir alle Elemente, die in dieser Liste enthalten sind,
zähle sie der Reihe nach auf,
damit ich beispielsweise irgendeine Form von Verarbeitung damit machen kann.
Das ist also eine sehr typische Verwendungsweise,
die man oft mal braucht. Die anderen sind eher exotisch,
also vollständig unterspezifiziert aufrufen
Bringt keinen Mehrwert. Das ist keine, also ich wüsste nicht,
was man mit einem solchen Ergebnis sinnvollerweise machen kann.
Natürlich kann man es verwenden. Man kann ja all diese
Variablen, die man hier hat, nachträglich noch instanzieren.
Das ist ja immer möglich. Aber es macht eigentlich keinen Sinn
, so vorzugehen. Gut, nächster Schritt war jetzt,
dass wir die Arrithmetik mit der Listenverarbeitung koppeln und die einfachste
Kopplung ist natürlich über das, die Bestimmen der Länge einer Liste.
Wir gucken uns wieder die positiven Beispiele für die Länge einer Liste an und fragen uns,
wie können wir mithilfe der Unifikationsführerlisten und zwar der rekursiven Unifikation das Reco-7-Universitätsmusters für Listen hier herangehen.
Und wir sehen also, dass immer wir,
wenn wir hier den ersten Kopf abtrennen, wir das auf das nächst einfachere Problem zurückführen können.
Also die Idee ist relativ simpel, nimm das als Rekursionsabschluss,
die leere Liste hat die leere Null und ansonsten trenne immer
ein Element von der Liste ab und damit führst du es auf den einfacheren Fall zurück,
was da gleichzeitig heißt,
du musst hier einsubtrahieren oder in der anderen Richtung eins drauf addieren.
Und das passiert hier, ich nehme immer die Restliste,
ich bestimme den Retten
Die Länge der Restliste, und ich muss,
weil ich ein Element ignoriert habe, natürlich das kompensieren,
indem ich das auf die Länge der Restliste noch eins drauf addiere,
damit die Länge der ursprünglichen Liste berechnet wird.
Das ist wieder eine naivrekusive Definition, wo die nach dem Reku-7-Abstieg noch eine Berechnung erfolgt.
Natürlich kann man das auch wieder in eine endrekusive Definition umbauen. Wir hatten ja aber bei den PA-Zeilen schon gesehen,
dass die naivrekusive Variante zumindest den Vorteil hat,
dass sie uns über Generate und Test auch Ergebnisse liefern kann,
in allen Instanziierungsvarianten, mit einem kleinen Schönheitsfehler,
den wir jetzt hier auch sehen, also kanonische Verwendungsweise,
gegeben die Liste, sagt mir die Länge, das ist natürlich klar, das geht.
Aber die umgekehrte Variante,
gib mir die Länge der Liste und ich gebe die Länge der Liste vor und möchte wissen,
welche Listen diese Bedingung erfüllen, dann kriege ich zwar das richtige Ergebnis,
natürlich uninstanziert, denn ich habe ja nichts gesagt darüber,
welche Elemente in der Liste drin sein sollen,
aber wenn ich dann nach Alternativen frage,
dann kriege ich wieder das Terminierungsproblem, Ganz natürlich,
weil ich eben über die Rekursion bis zur Dreihe gekommen bin.
Und danach versuche, eine weitere Liste zu finden,
die auch drei Elemente enthält.
Und die wird es nicht geben. Und die Suche nach
dieser weiteren Liste, die bringt eben dieses Terminierungsproblem hier herein.
Wir könnten das schon wieder als kleiner Vorgriff.
Wir könnten das hier natürlich reparieren, indem wir sagen,
nachdem wir das erste Mal ein Ergebnis gefunden haben, dann mache nicht weiter.
Also das Ergebnis wird ja immer produziert durch den Rekursionsabschluss hier.
Wann immer wir dies erfolgreich angewendet habe, dann suche nicht weiter,
dann betrachte die diesen Rekursionsschritt nicht mehr, denn der führt dann ins Unendliche.
Nur wenn das gescheitert ist, dann gucke auch diese Liste an,
aber das ist nicht monotonisch schließen, da brauchen wir den Cut,
wir müssen hier danach genau sagen, Schluss jetzt,
hör auf zu suchen und dann können wir auch verhindern,
dass dieses Dominierungsproblem entsteht.
Aber das gucken wir uns in Ruhe an,
wenn wir so weit sind, dass wir uns darüber Gedanken machen,
wie wir den Suchraum manipulieren können.
Hier nochmal das Rekursionsschema, also als erstes die kanonischen Verwendungsweise Liste ist gegeben,
Länge der Liste ist gesucht und dann sehen wir jetzt hier,
dass beim rekusiven Abstieg immer diese Gleichungen aufgebaut werden.
Also X berechnet sich aus einem X1,
was ich noch nicht kenne und durch Addition einer 1.
Und da muss ich eben X1 berechnen und zwar dadurch,
dass ich die Länge der um eins verkürzten Liste versuche zu berechnen.
Auch dann weiß ich wieder,
ich kann es auf die Länge der wiederum eins verkürzten Liste
zurückführen und was immer der wertvolle X2 ist, da drauf,
ADA1 ergibt dann den Wert von X1 und der geht hier wieder ein.
Aber ich kann diese Auswertung eben noch nicht machen,
weil die Werte noch nicht bekannt sind
Funktionale Auswertung, geht eben nur, wenn ich die Werte
schon gegeben habe. Das ist erstmalig hier unten der Fall.
Hier kriegt das X3 jetzt den Wert null zugewiesen und
dann bin ich in der Lage, diese Addition durchzuführen.
Und genau das passiert hier im Rekursiven Aufstieg wieder.
Dass also nachdem ich in die Tiefe abgestiegen bin, im Aufstieg dann,
also und das sieht man in der Definition eben sehr schön,
die arrometische Berechnung passiert, nachdem ich den Rekursiven Aufruf gemacht habe.
Erst wenn der abgeschlossen ist, dann kann ich diese Addition durchführen.
Und damit ist das Ganze nicht entrekursiv und Kann nicht entsprechend optimiert werden.
Ich muss tatsächlich die vollständige Rekursion durchführen. Das gleiche Bild haben wir auch,
wenn wir das umgekehrt instanziert aufrufen,
also jetzt ist die Länge vorgegeben und wir wollen die Liste wissen,
dann sehen wir hier,
dass das Ganze uninstanziert wieder völlig uninstanziert aufgerufen wird,
mit frischen Variablen, über die wir nichts wissen.
Das ist also genau dieser völlig blinde Charakter der Suche, der hier passiert.
Wir sagen nur einfach, dann versuchen wir es eben mit
zwei uninstanzierten Variablen. Alles, was wir uns merken,
dass wenn wir hier mal ein X1 bekommen,
dann wird es so in den Wert von X eingehen.
Und wir wissen, wenn wir mal ein N1 haben,
dann muss die Gleichung in 1 plus 1,
wertet funktional zu 3 aus aufgehen.
Aber beide, also nein, Letzteres,
das hier können wir natürlich bereits beim rekursiven Abstieg feststellen.
Also wir wissen, das X wird eben eine uninstanzuierte Variable
gefolgt von einer uninstanzuierten Variable, gefolgt von einer uninstanzuierten Variablen zu sein.
Das wissen wir hier unten bereits. Aber das können wir
natürlich wieder nicht berechnen. Das können wir erst berechnen,
wenn wir im rekursiven Aufstieg wieder nach oben kommen
Gut, der Lackmust-Test, jedes Unifikationssystems ist das Appent Zweialisti,
werden immer jemand hingeht und sagt,
ich habe hier eine Form von Unifikation entwickelt und das muss nicht Thermunifikation sein,
so wie wir das hier betrachten.
Das können allgemeinere Formen der Unifikation sein, also beispielsweise Graf-Unifikation.
Und der Entwickler möchte demonstrieren, dass sein System semantisch korrekt funktioniert.
Dann implementiert er das Appent für dieses Beispiel und zeigt am Beispiel
des Appents, dass tatsächlich die gewünschte Leistung erreicht wird.
Listen kann ich immer definieren, wenn immer ich zweistellige Strukturen habe,
dass die einzige Voraussetzung, die ich brauche,
um mit Listen umzugehen und dann kann ich zeigen,
dass über diesen Strukturen die Unifikation tatsächlich funktioniert.
Wenn ich in der Lage bin, dass er pennt, richtig zu implementieren.
Wir gucken uns also wieder die positiven Beispiele an und wir sehen, aha,
wann immer, hier vorne die erste Liste leer ist,
dann ist die, also wir müssen unser erstes Prädikat hier mal angucken.
Hier ist die erste Liste gegeben, die zweite Liste und
wir wollen die erste und die zweite Liste zusammenhängen,
sodass sich auf der dritten Argumentstelle die Ergebnisliste ergibt.
Und wir sehen, wann immer die erste Liste leer ist,
dann ist die Liste auf der zweiten Argumentstelle gleich der Liste
auf der dritten Argumentstelle
Und ansonsten können wir wieder unser Rekursionsmuster anwenden. Wir trennen
das erste Element ab. Und dann sehen wir, dass das,
was übrig bleibt, die Liste B, verkettet mit der Liste CD,
die Restliste ergibt, wenn wir dort das gleiche Element,
was wir hier abgetrennt haben, hier auch abtrennen.
Und das können wir rekursiv fortsetzen, wenn wir hier A,
B abtrennen und hier A, B abtrennen,
dann bleibt hier, verkette die Liste C mit der Liste.de,
zur Liste CDE übrig.
Oder wenn wir nicht so radikal sind,
wir trennen nur eins ab,
dann haben wir das Problem auf den Fall der Zweierliste zurückgeführt
Also, wir trennen hier ein A-A-P-P-Prainier ein A ab,
dann heißt es, nimm zwei Elemente hier,
nimm zwei Elemente hier und mache diese Vierer-Liste da draus.
Und das ist genau der Fall, der unter fünf steht. Also,
wir sehen wieder das Schema, wir haben hier oben den Rekursionsabschluss,
wir können hier draus den Rekursionsschritt generieren,
indem wir diese Liste zerlegen, in Kopf- und Restliste,
diese Liste zerlegen, in Kopf- und Restliste und dafür sorgen,
dass im Körper der Klausel die Restliste hier mit dieser Liste verkettet wird,
sodass sich diese Ergebnisliste ergibt,
also die Restliste,
die hier auf der Ergebnisliste
übrig bleibt
Hier ist das also nochmal mit dem Kopfseparator dargestellt und zwar nur der Rekursionsschritt jetzt,
die Beispiele vier bis sechs in einer leicht anderen Notation.
Wir hatten uns ja angeguckt,
dass wir für Listen ganz unterschiedliche Notationen verwenden können,
die alle semantisch äquivalent sind,
die im Internen der Maschine vollkommen gleich sind,
nur auf der Oberfläche unterschiedlich dargestellt werden.
Also diese Liste hier ist nämlich genau die einer Liste,
die nur A enthält, diese hier. Hier,
an dem haben wir nichts geändert und hier haben wir den
ersten Kopf abgetrennt und es bleibt übrig, die Liste BC.
Und das ist die Liste, die besteht aus A,
B und C,
also diese hier
Und das Gleiche haben wir hier gemacht, die Zweierliste AB,
haben wir zerlegt, in einen Kopf A und eine Restliste B.
Und hier hinten haben wir die Vierer-Liste A, B,
C, D zerlegt, in den Kopf A und die Restliste B, C, D.
Und das Gleiche dann für das sechste Beispiel.
Und hier sehen wir dann eben sehr schön,
wann immer wir hier ein A abtrennen, wir müssen,
wenn wir hier das erste Element abtrennen,
dann ist das gleich dem ersten Element, was wir hier hinten abtrennen müssen.
Und damit haben wir die Definition zusammen. Rekursionsabschluss ist die Lehreliste
verkettet mit irgendeiner Liste, ist wieder die gleiche Liste.
Es ändert sich nichts daran. Und ansonsten müssen wir hier
den Kopf abtrennen, hier den Kopf abtrennen.
Die Restliste mit dieser Liste verketten, sodass wir die Restliste
hier erhalten
Das ist alles, was wir haben, was wir brauchen.
Und wir gucken uns das Ganze jetzt an,
wie es funktioniert, die Instanziierungsvarianten mit zwei Argumenten instanziert,
einem Argument uninstanziert, also die kanonische Verwendungsweise,
wie in der funktionalen Programmierung, die haben wir hier oben,
verkette die Liste AB mit der Liste CDE und gib mir das Verkettungsergebnis,
das liefert eben die Liste ABCD.
Ich kann jetzt aber jetzt umgekehrt auch machen,
indem ich sage, was muss ich nehmen,
damit es verkettet mit der Liste CDE,
die Liste ABCDE ergibt
Und ich kriege als Ergebnis heraus,
es ist Dealers der AB. Oder ich kann fragen,
mit was muss ich A, B, verketten,
sodass abc.de entsteht und als Ergebnis ist die Liste CDE,
muss ich hinten dran hängen, damit die gewünschte Ergebnisliste entsteht.
Hier sind die Rekursionsschematar dafür, jedenfalls in Auszügen,
also für die kanonische Verwendungsweise.
Ich reduziere die erste Liste und ich instanziere auf der zweiten
Argumentposition dieses Listenpattern mit einer unbekannten Restliste und die unbekannte Restliste
soll hier ermittelt werden
Ich mache das Ganze nochmal, es wird der nächste Kopf abgetrennt,
dafür wird bereits wieder die Ergebnisliste produziert mit dem Kopf,
den ich hier jeweils abgetrennt habe,
hier oben das A, hier das B,
hier im dritten Schritt das C und der Verkettung,
dass das Ergebnis mal A, gefolgt von einer Restliste sein wird,
die Restliste ergibt sich hier zu B,
gefolgt von einer Restliste und diese Restliste 2 ergibt sich zu C,
gefolgt von einer Restliste.
Und dann komme ich zum Rekursionsabschluss,
jetzt habe ich das Ding hier auf Null reduziert,
es ist die Lehreliste, dann ist der Rekursionsabschluss,
kommt zum Tragen,
Und es wird der Wert der zweiten Argumentstelle mit dem Wert der dritten Argumentstelle unifiziert.
Jetzt kenne ich den Wert für 3. Das ist nämlich die
Liste.de. Damit kenne ich den Wert für X-2. Das ist
die Liste c.de. Damit kenne ich den Wert für X-1.
Das ist die Liste B-C.de. Und ich kenne den Wert für X.
Das ist die Liste A-B-C.de. Das weiß ich bereits,
wenn ich hier unten am Rekursionsabschluss angekommen bin.
Das heißt, es ist wieder eine Endrekursive Definition.
All das, den Rekursionsaufstieg brauche ich nicht mehr.
Und ich brauche auch die ganzen Zwischenergebnisse,
die ich hier mal ermittelt habe, nicht mehr.
Das heißt, das Ganze ist Speicherplatz effizient
Ist kein großes Wunder. Wir hatten ja das äußere Erkennungsmerkmal der Endrekursion,
genau wie in der funktionalen Programmierung,
in der funktionalen Programmierung sehen wir eben, es ist die letzte Berechnung,
die in der Funktion passiert, im Körper der Funktion.
Hier ist es das letzte Teilziel, was aufgerufen ist,
das muss der rekursive Abstieg sein.
Und das ist genau der Fall. Hier ist nur,
im Körper ein Teilziel vorhanden, also es ist das Letzte.
Und damit ist die Endrekursion gegeben. Hier unten nochmal
das Gegenbeispiel mit der anderen Instanzierungsvariante, sage mir,
die Liste L1, die ich mit DE verknüpfen muss,
sodass ABCD,
ABCDE entsteht
Und auch hier sehen wir wieder,
dass die Ergebnisliste aufgebaut wird, wieder in diesem Bekannten,
nun inzwischen schon sehr vertrauten Art und Weise,
dass von außen nach innen instanziert wird.
Zuerst weiß ich, es ist das gepunktete Paar aus A
und einer Restliste, im zweiten Schritt weiß ich,
dass die Restliste diese sein wird.
Im dritten wird sie diese sein,
also jeweils hier reinzusetzen und im vierten Schritt weiß ich dann,
dass die Liste
die läste.de sein wird
Gut. Und nein, im vierten Schritt, sorry,
im vierten Schritt weiß ich, dass es die leere Liste sein wird.
Als es hier die erste Variable, die ja installiert werden muss.
Also hier wird extra mit der leeren Liste installiert.
Und dann steht hier am Rekursionsabschluss der Wert für X-Fest.
Es ist die Liste A, B, C und nix weiter.
Das heißt, auch hier bin ich nach dem rekursiven Abstieg fertig.
Ich habe das Ergebnis und kann es nach außen mitteilen.
Das ist dann die dritte Variante, die ist jetzt
nicht mehr, doch die ist eine kleine Variation davon.
Jetzt ist nämlich das zweite Argument uninstanziert. Das ist das,
was von der Rekursion ja überhaupt nicht betroffen ist.
Wenn man sieht, das wird die Definition nochmal angucken,
dann sehen wir rekursiver Abbau oder Konstruktion, je nachdem,
wie wir es installieren, passiert ab dem ersten und dem dritten Argument.
Das zweite Argument wird einfach nur durchgereicht. Was immer der
Wert hier ist, muss auch der Wert im Kopf der Klausel sein.
Und das heißt, das jetzt reden wir über die Argumentstelle,
die von dem Rekursionsabstieg überhaupt nicht betroffen ist.
Und genau das sehen wir dann hier auch. Wir trennen immer
sowohl in der ersten als auch in der letzten Liste.
Sie sind beide installziiert, können wir den Kopf ab,
über die CO-Referenz natürlich, dass die Dinge gleich sind,
die wir dort abtrennen, sonst ist es keine Lösung.
Und das machen wir, bis wir bei der leeren Liste gelandet sind,
auf der ersten Argumentstelle, dann wissen wir,
dass auf der dritten Argumentstelle das Ergebnis steht, was wir auf der zweiten suchen.
Ja, dann wird über die CO-Referenz zwischen zweiter und dritter Argumentstelle,
wird die Vagable instanziert und wir haben beim Abstieg nur diese Choreferenzgleichung aufgebaut,
X ist gleich X1, gleich X2,
gleich X3 und jetzt kennen wir X3. Das ist der,
also da ist auch keine weitere Berechnung mehr erforderlich.
Das Ergebnis steht da unten bereits fest
War in diesem Falle ja auch einfach,
weil wir gar nichts aktiv konstruieren müssen, sondern weil das Ergebnis
nur durch Wegstreichen jeweils des Kopfes aus dieser Liste entsteht.
Und hier unten haben wir die Ergebnisliste und können sie nach außen kommunizieren.
Also auch das ist endrekursiv. Hier dann weitere,
wir können kühn werden, wir haben drei Argumente,
jetzt lassen wir mal zwei instanziert und die erste Variante ist tatsächlich etwas,
was man verzeiht zur Zeit mal ganz sinnvoll einsetzen kann,
nämlich die Verwendungsweise,
ich habe hier eine Liste,
bitte zerlege sie mir in alle Paare von Teillisten
Und die Antworten sind natürlich klar,
die Liste mit den fünf Elementen kann zerlegt werden in die
Lehreliste und die Liste mit den fünf Elementen oder die Liste,
die hier nur A enthält und hier die restlichen Elemente B,
CDE oder die Liste,
die AB enthält und die restlichen Elemente CDE und so weiter und so fort.
Auf die Art und Weise kann man alle Zerlegungsvarianten für eine
gegebene Struktur durchprobieren. Und das kann man jetzt beliebig fortführen,
indem man fragt, da kenne ich keine gute Verwendungsweise dafür.
Also ich habe eine Liste CDE gegeben, sage mir bitte,
womit ich die kombinieren kann und was ist das Ergebnis dieser Kombination
Und dann kriegt man eben genau das heraus, dass das,
was hier instanziierte Elemente gewesen sind, sind jetzt uninstanziierte Elemente.
Also irgendeine, eine,
eine einer Liste verkettet mit dieser Liste, ergibt die Liste,
wo das einzige Element dieser Liste hier als erstes drin enthalten ist.
Wenn das eine Zweierliste ist,
dann seid es die ersten beiden Positionen in der Ergebnisliste,
wenn es eine Dreierliste ist und so weiter und so fort.
Gut, das sind nur dem Nachweis,
dass es tatsächlich narrensicher ist, hier auch nochmal die Rekrusionsschemata.
Ich glaube nicht, dass wir jetzt im Detail da durchgehen müssen.
Hier haben wir wieder beide Varianten kombiniert.
Wir bauen also auf der ersten Argumentposition
eine Liste auf
Die sukzessive konstruiert wird und auf der zweiten Argumentposition geben wir einfach immer nur,
das sind die Ys hier, die Ergebnisse weiter.
Da wird also eine Chorreferenzgleichung aufgebaut. Und jetzt beruht also der ganze Trick der Berechnung darauf,
dass ich auf der dritten Argumentstelle auch immer,
dass ich auf der dritten Argumentstelle eine Liste rekrusiv abbaue,
indem ich immer ein Element entferne und die Restliste betrachte.
Und auf die Art und Weise wird, wird der hintere Teil,
also der Wert von Y 2 in jedem Schritt erzeugt und
der vordere Teil ergibt sich eben genau aus dieser Chorreferenz,
diesen geschachtelten Listen, die ich hier beim rekursiven Abstieg aufbaue.
Gut, das heißt also,
wir haben hier offensichtlich eine Definition, die endrekursiv ist,
wir können es auch vollständig und der Spezifizier aufrufen,
in den meisten Systemen ist es eingebaut, unter dem Namen erpennt.
Ich habe es, damit es auf die Folien noch passt,
ein bisschen abgekürzt und abgenannt.
Wenn wir uns nochmal die Definition angucken, die wir hier haben,
dann hätte man eigentlich etwas bemerken sollen.
Die Definition haben Sie alle schon mal gesehen,
und zwar wo
Also im Prinzip, dass die Struktur dieser Rekursionsgleichung Gerg,
ich habe es mitgekriegt, ja, ich möchte das alles sehen.
Also, wer hat eine Vorstellung davon,
wo das schon mal aufgetaucht ist?
Das sind mir noch zu wenige. Lass uns mal Revue passieren,
welche Strukturen haben wir bisher schon so alle angeguckt?
Wir haben mit PA-Notzahlen angefangen. Wir haben dann einen Exkurs
in die Arrithmetik gemacht. Arrithmetik kommt hier offensichtlich nicht vor.
Danach haben wir die Listen aufgemacht. Bei der Listenverarbeitung offensichtlich
nicht. Also muss es
im Bereich der PA-Notzahlen
gewesen sein
Ja, lösen Sie das Rätsel? Ja, nur Addition
. Das ist nichts anderes als die Addition von Piano-Zahlen.
Und ich meine, was wollen wir auch anderes erwarten?
Was wir hier machen, ist, wir verketten Listen.
Listen haben immer eine Länge. Und das ist nichts anderes
als addiere eine Null mit irgendeiner Zahl und es kommt diese Zahl heraus.
Ich brauche mir das nur über die Längen der Listen zu veranschaulichen.
Und dann weiß ich eben eine Zweierliste verkettet mit einer Dreierliste
, das ist eine Fünferliste.
Und das ist die Addition, nichts anderes.
Nur hatten wir vorher eben S von S von S von S-Strukturen
. Und jetzt haben wir Listen mit einer bestimmten Länge
Also diese Strukturen, ihr sind leicht aufwendiger als diese einstelligen Strukturen,
die wir für die PA-Nutzahlen benutzt haben,
aber im Prinzip können wir die gleiche Art von Arrithmetik damit betreiben.
Und das ist eigentlich das Schöne daran,
dass man hier sehr, sehr deutlich sieht,
dass diese verschiedenen Welten eigentlich gar nichts grundsätzlich anderes darstellen,
dass wir die gleichen Programmstrukturen sowohl für die PA-Nu-Arrithmetik verwenden können,
als auch hier für die Listenverarbeitung.
Und wir können das Spiel fortsetzen,
auch diese Definition für die Länge einer Liste
ist schon mal vorher aufgetaucht
Genau an der gleichen Stelle. Als wir nämlich die Tiefe der Einwettung einer PA-Notzahl
berechnet haben oder nichts anderes als wandeln wir eine PA-Notzahl in einer Intecher-Zahl um.
Das ist die gleiche Programmstruktur, die wir dort hatten,
nur dass jetzt hier eben Listen stehen, statt es von irgendetwas.
Decken Sie sich einfach diese Ecke, Klammer, Underscore,
senkrechter Strich, durch S von ersetzt und hier hinten eine Runde,
Klammer zu, dann ist es exakt das gleiche Programm.
Und das ist eine Art von Denken,
oder wo ich das Ziel habe,
dass sie damit ein bisschen vertraut werden,
dass sie Lösungen von einem Gebiet auf ein anderes Gebiet übertragen können,
weil sie die Struktur der Lösung oder die Struktur des Problems erkennen.
Und dann darauf auf der Basis entscheiden können,
dass die gleiche Struktur von Programmen auch auf in dem neuen
Gebiet verwendet werden können, mit den gleichen Stärken und Macken.
Dieses Terminierungsproblem, das haben wir vorhin gerade diskutiert,
dieses Terminierungsproblem nach dem ersten Ergebnis,
das hatten wir bei den PA-Netzzahlen genau in der gleichen Weise.
Das heißt, wir können alle Überlegungen,
die wir bei den PA nur zahlen,
für dieses Sorte von Programmen angestellt haben,
hier wieder übernehmen.
Alle Zusicherungen für die Verwendungsweise
und so weiter
Auch der Trick, wie ich das in eine entrekrusive Definition
umwandeln kann, wie ich dieses Terminierungsproblem beseitigen kann,
das passiert bei den PA-Nutzerlen genauso wie hier.
Das ist eigentlich das Schöne daran, dass die Logikprogrammierung,
die zugrunde liegende Problemstruktur tatsächlich deutlich macht. Und damit man sie sehen kann,
und da bitte ich, dass sie mir das verzeihen,
damit man die Struktur erkennen kann,
muss ich relativ kurze Bezeichner wählen und relativ kurze Bezeichnungen,
sowohl für die Programme als auch für die Bajablen
Der Überblick über die zugrunde liegende Struktur geht verloren,
in dem Augenblick, wo ich dort fünf teilige Variablennamen verwende,
dann sehe ich nicht mehr, wie die einzelnen Argumentpositionen eigentlich zusammenhängen.
Und dann verliere ich auch den Überblick über die zugrunde liegende Problemstruktur.
Das ist der Grund, weshalb ich an vielen Stellen
von dem Prinzip abweiche, sprechenden Namen zu verwenden.
Oder die Namen wenigstens so weit zu reduzieren,
dass auch ungefähr deutlich wird, was gemeint ist.
Also das soll der Kopf sein, deshalb heißt das Ding K.
Und das heißt eben hier verkettete Liste,
also rudimentär ist die Intuition noch zu erkennen,
aber eben nur noch rudimentär
Einen zweiten Aspekt kann man hier sehr, sehr schön beobachten.
In den Übungen taucht es immer wieder auf,
dass viele von ihnen eine Scheu haben, solche Listen,
Muster oder überhaupt Strukturmuster im Kopf einer Klausel tatsächlich zu verwenden.
Sondern lieber eine neutrale Signatur verwenden für den Klauselkopf,
also in diesem Falle so etwas wie ab L1, L2,
L3 und dann im Körper der Klausel erstmal die Zerlegung machen,
dass sie sagen,
ja L1 muss jetzt zerlegt werden im Kopf
und was haben wir hier,
Und dann das Gleiche eben auch für Liste
3. Gleich der Kopf und in diesem Falle die verkettete Liste.
Und dazu geht es weiter. Kann man machen,
ist überhaupt nicht aus logischer Sicht überhaupt kein Problem.
Hat allerdings zwei Nachteile. Das eine ist die Struktur des Problems,
geht wieder verloren. Es ist eine Art von imperativen Denken,
das hier durchguckt. Ich führe erst mal vajabel,
ich deklariere bei Jablen und hinterher versorge ich die Vagablen mit
einem Wert
Und dieses Versorgen mit einem Wert passiert zu einem relativ späten Zeitpunkt.
Und wenn man sich da ungeschickt anstellt, kann es passieren,
dass man die Rekursionen bereits macht, ehe man diese vajablen Werte zugewiesen hat.
Und dann kann es unter ungünstigen Umständen dazu führen,
dass man einen Rekursiven Aufruf mit völlig unrestrinierten Vagablen macht und ein Terminierungsproblem bekommt.
Das ist Gefahr Nummer eins. Das kann nicht passieren,
wenn ich die Bindung oder die, das ist ja eine Testoperation, die hier steht.
Die sagt mir, ja, mache das nur,
solange du das Ding noch in Kopf und Restliste zerlegen kannst.
Wenn das nicht mehr geht, scheitert die Unifikation.
Und wenn ich das bereits im Klausel-Kopf mache,
dann wende ich diese Restriktion, so zeitig es irgend geht, an.
Und dann vermeide ich auch, dass ich in die Falle laufe,
dass möglicherweise durch Uninstanzierte Vajable überhaupt ein Tabinierungsproblem entsteht.
Und ich kann in diesem Fall hier zum Beispiel erreichen,
dass diese Klausel überhaupt gar nicht erst angefasst wird,
in dem Augenblick, wo die Liste leer ist.
Dann scheitert nämlich bereits die Unifikation des Kopfes und das System
kommt überhaupt nicht auf die Idee, den Körper aufzurufen.
Wenn sie es so machen, muss nochmal ein Prozeduraufruf mit den Konsequenzen,
dass die Vajablenbindung auf den Stack gehören und so weiter,
vorbereitet werden und durchgeführt werden,
die vajablen Substitutionen muss gemacht werden und so weiter,
um dann festzustellen, oh, die Modifikation scheitert nicht.
Das hätte man auch schon früher wissen können. Und dann hätte man
diesen Aufruf des Körpers erst überhaupt nicht gestartet. Also es ist auch ein kleines Effizienzproblem,
was damit verbunden ist und die Botschaft ist,
versuchen sie immer, solche Restriktionen, die irgendwo vorhanden sind,
so zeitlich es irgend geht, in die Berechnung einzubringen.
Und das heißt eben, was immer hier steht, hier hinein substituieren.
Was immer hier steht, hier hinein substituieren. Und wenn das bequem ist für die Entwicklung,
diese Denkweise, ich mache erst mal eine vajablen Deklaration und hinterher versorge ich die Vagablen mit Werten,
Können sie ja ruhig so machen, ist ja nichts Schlechtes dran,
aber hinterher kann man nochmal einen Bereinigungsdurchgang machen, indem man sagt,
ach ja, klar, das, was hier steht,
könnte ich ja auch gleich in den Kopf der Klausel schreiben,
mit den damit verbundenen Vorteilen.
Die Vorteile sind sogar noch ein bisschen größer,
jedes Prologsystem macht ein Hashing über dem Prädikatsnamen,
das ist selbstverständlich, aber teilweise auch über den Argumentenbelegungen,
mindestens über dem ersten Argument.
Das macht jedes Prologsystem, einige machen es auch über
mehreren Argumenten, wenn mehrere da sind.
Und auf diesen Vorteil des effizienten Zugriffs zu den Klauseln
verzichten sie natürlich in dem Augenblick,
wo es nichts zum Hashing gibt
Je mehr Restriktionen hier drin stehen, umso stärker kann das System einschränken,
welche Klauseln kommen überhaupt nur in Frage für das gegebene Ziel.
Also diesen Effizienzvorteil, den verschenkt man sich,
wenn man hier auf die Art und Weise die Konstruenz zu spät in die Berechnung einbezieht.
Das gilt natürlich genauso auch für diese Chorreferenz hier.
Man hätte auch das schreiben können in Form einer Regel,
indem man sagt, Ja, das ist semantisch völlig äquivalent.
Es ist überhaupt nichts anderes, hat eben nur den Nachteil,
dass sich erst mal die Regel anwenden muss,
um dann im nächsten, sorry, keiner schreit auf, das sollte.
Natürlich ein Kopfseparator sein hier. Ich muss die Regel erst mal anwenden,
um dann festzustellen, oh, geht leider gar nicht anzuwenden,
weil die Liste, die ich hier habe, eine Lehre lässt ist.
Wenn ich das Gleiche vorne reinschreibe, so wie es dort gemacht ist,
erkennt das Hash-Verfahren sofort, das ist eine Klausel,
die auf eine nicht leere Liste überhaupt nicht anwendbar ist.
Und da muss ich es gar nicht erst probieren.
Also das sind so kleine Effizienzvorteile, die daraus erwachsen.
Wichtiger ist für mich der Aspekt, dass eben,
wenn ich es gleich als Muster in den Kopf hineinschreibe,
dann sehe ich eben solche Effekte wie Roh, hier gibt es offensichtlich eine Symmetrie,
was immer der Kopf hier ist, muss auch der Kopf dort sein.
Und das ist die gleiche Form von Symmetrie,
die ich bei der Addition eben auch hatte.
Das möchte ich, dass man das sehen kann und
dafür ist diese Notationsweise mit den Listen ideal geeignet.
Ich kenne keine bessere
Okay, das waren die Grundlagen der Listenverarbeitung.
Wir gucken uns jetzt eine ganze Reihe von Beispielen an,
in denen wir mit Listen umgehen,
weil eben Listen ein so universell verwendbares Mittel sind.
Insbesondere im Hinblick auf die Flexibilität, was die Erweiterbarkeit der Listen angeht.
Und wir werden später auch noch dazu kommen,
dass wir bis jetzt, reden wir ja hier nur über Listen,
die tatsächlich Konstanten enthalten.
Wir werden das natürlich dann noch erweitern auf Listen,
die Bajablen enthalten und welche Tricks wir da mitmachen können.
Das ist dann bei den Differenzlisten wird das interessant
Wo wir das Verketten von Listen noch enorm effizienter machen können,
als es über die klassische rekursive Definition,
die wir hier eben gesehen haben, möglich wäre. Ja,
was ich auch noch nicht diskutiert habe, ist die Frage der,
ich gehe nochmal einen Schritt zurück, wir hatten gesehen,
dass wir hier offensichtlich ein endreklusives Prädikat haben, alle,
die schon mal Listenverarbeitung im funktionalen Paradigma gemacht haben sollten,
jetzt mal überlegen,
wie das erpennt in der funktionalen Welt aussieht.
Es ist endrekursiv,
es ist nicht endrekursiv
Dann können Sie mal überlegen, woran liegt das?
Woran liegt der Unterschied? Wenn es denn einen gibt?
Also entweder ist es genauso oder es ist unterschiedlich und wenn ja,
wenn es unterschiedlich ist, worin liegt eigentlich der Unterschied?
Warum sieht es hier anders aus als in der funktionalen Programmierung?
Da kommen wir bei der funktionalen Programmierung dann nochmal drauf.
Zurück. Jetzt also die angekündigten Beispiele, einfacher,
der ein sehr einfacher Fall ist,
dass Sufix einer Liste,
ein Sufix einer Liste kriege ich ja einfach nur dadurch,
dass ich das erste Element einer Liste wegstreiche und
den Rest als Sufix verkaufe
Und das ist jetzt definitionsabhängig, ob die Liste
selbst so fix von sich sein soll oder nicht.
Hier ist es so implementiert, dass die Liste sich selbst als Suffix enthält.
Das kann man anders machen, wenn man das möchte.
Das können Sie sich auch überlegen, was dann geändert werden muss.
Das ist eine Vereinbarungsangelegenheit. Dann, wenn man das also macht,
die Annahme macht, dann ist es relativ einfach,
dass Suffix einer Liste ist, die Liste selbst.
Und wenn ich das erste Element einer Liste abtrenne,
dann kriege ich wieder einen Suffix davon.
Und das ist also
die Agenten
Funktionsweise ist dann also ganz simpel. Ich gebe eine Liste
vor, ich kriege mit dem Rekussionsabschluss das erste Ergebnis,
das ist die Liste selbst. Wenn ich nach Alternativen frage,
komme ich in den Rekussionsschritt, der Rekussionsschritt, sagt mir,
Streiche auf der zweiten Argumentstelle, das erste Element weg.
Es interessiert uns nicht länger, deshalb die anonyme Variable hier
und erkläre den Ruf für das Ganze rekursiv auf.
Und im Rekursiv-Aufruf wird jetzt mit der um eins verkürzten Liste dieser Rekursionsabschluss wieder aktiviert,
der sagt, ja, das Ding, was ich da kriege,
ist ein Suffix, das gibt das zweite Ergebnis.
Und wenn ich jetzt wieder nach Alternativen frage,
wird wieder von dieser Liste hier das erste Element abgetrennt und weggeworfen, Ganze rekrusiv aufgerufen,
mit dieser verkürzten Liste und über den Rekulsionsabschluss kriege ich das nächste Ergebnis.
Das ist dieser hier. Und wenn ich wieder ein Rekulsionsaufschritt mache,
weil ich dazu aufgefordert werde, wird wieder ein Element abgetrennt.
Das ist jetzt das Letzte, was wir haben.
Jetzt bleibt die leere Liste übrig und auch die leere Liste
wird über diesen Rekulsionsabschluss nach außen übergeben.
Denn hier gibt es ja keinerlei Restriktionen für die möglichen Wertebindungen.
Jede Elibiliste wird als Ergebnis übergeben. Das ist also die eine Verwendungsweise,
die andere Verwendungsweise wäre beispielsweise von welchen Listen ist die Liste
AB ein Sofix
Ich kenne keine sinnvolle Verwendungsweise dafür. Aber wir werden gleich noch sehen,
dass es tatsächlich, ne, es vorgeprescht für die Richtungsunabhängigkeit von Prädikaten,
die darauf aufbauen, kann es tatsächlich von Bedeutung sein,
dass das auch richtungsunabhängig ist.
Also die Ergebnisse sind dann eben über den Rekursionsabschluss wieder,
ja, die Liste selbst ist natürlich eine von der AB so fix ist.
Und ansonsten wird jetzt, weil uninstanziert aufgerufen,
hier auf der zweiten Argumentposition, das ist ja hier die uninstanzierte Argumentposition,
werden hier jetzt Listen konstruiert.
Dort, wo eben vorher, konstanziiert aufgerufen,
Listen abgebaut worden sind, werden sie jetzt hier konstruiert.
Und das ist das Ergebnis. Als erstes wird eine
anonyme Variable davor gesetzt. Diese anonyme Bayable,
die ist jetzt eine frisch vom System generierte, interne Variable,
die davor gebaut wird. Und wenn ich nach Alternativen frage,
dann wird eben eine weitere Variable dort davor gebaut.
Und auch hier sehen wir wieder, dass das Aufbauen von außen nach innen immer passiert.
Also die alte Variable 298 ist nach wie vor das erste Element.
Denn das ist das, was ich hier vorher gemacht habe.
Ich unifiziere immer nur hier hinten rein. Und hier hinten
wird jetzt eine neue Liste hineinunifiziert, mit einer frischen Bayablette.
Und wenn ich das wieder aufrufen würde,
würde die neue Variable einer dritten Position auftauchen.
Auch hier sieht man also, dass dieser Listenaufbau immer von
außen nach innen passiert. Und das ist genau der Grund,
weshalb das Ganze so schön richtungsunabhängig ist und endrekursiv gemacht werden kann,
weil ich eben das Ergebnis schon in der Hand habe,
auch wenn es noch nicht vollständig spezifiziert ist.
Aber ich kenne es schon. Das Pendant zum Suffix
ist natürlich das Prefix zu einer Liste.
Ich habe also wieder Listen gegeben und ich möchte wissen,
was sind die ersten Elemente
Einer Liste. Da ist die Definition jetzt ein bisschen komplizierter.
Hier müssen wir jetzt wirklich rekrusiv abbauen. Und zwar,
wie wir das vorhin beim Appent auch gemacht haben,
dass wir hier auf der Liste ein Element wegnehmen,
dann müssen wir es auch hier hinten wegnehmen.
Und wenn denn, dass hier vorne ein Prefix einer Liste ist,
der Liste R ist, dass MP ein Prefix der Liste R ist,
dann ist auch das um eh erweiterte Prefix ein Prefix,
der der UE erweiterten Restliste.
Und natürlich ist die leere Liste Prefix,
na nicht natürlich, aber wir definieren es hier so,
die leere Liste ist Prefix,
jeder beliebt in anderen Liste
Und wenn ich das jetzt also aufrufe in der normalen Verwendungsweise wieder,
also gib mir das Briefix,
der Liste Arbit oder alle Präfixe, der lässt ABC,
dann kriege ich mit dem Rekursionsabschluss das erste Ergebnis, die leere Liste ist es.
Und wenn ich nach Alternativen frage,
komme ich über den Rekursionsschritt dazu, dass ich sage,
okay, jetzt trenne ich das A ab,
bilde die Präfixe der Restliste durch diesen Rekursieben-Aufruf und übernehme das E in die erste Ergebnis,
in die Präfixliste, in die hier uninstanziierte Liste.
Also auch hier wieder, das wird instanziiert aufgerufen,
da wird rekursiv abgebaut, das wird uninstanziert aufgerufen,
da wird konstruiert
Und hier auf dieser wird jetzt das A genommen und als erstes Element
in eine Liste gesetzt, deren Restliste ich noch nicht kenne.
Und über den Recosiven-Aufruf komme ich auf diesen Rekursionsabschluss und der sagt,
ja, ich kenne eine passende Restliste, ist die leere Liste.
Und damit kriegen wir die Einerliste hier heraus.
Und wenn wir das Ganze wieder rekursiv aufrufen, wird wieder
ein Element hier entfernt, aber ein neues Paar damit gebildet.
Und dieses neue Paar ist jetzt der Top-Level, der eingebetteten Liste.
Also die Restliste, die B enthält.
Und über den Rekursionsabschluss kriege ich eben hier hinten rein wieder,
das Ganze wird wieder von außen nach innen konstruiert,
hier hinten rein den Listenabschluss.
Und damit steht das nächste Ergebnis nämlich AB fest
Und die Restliste leer, die ich eben hier hineinodifiziert habe,
diese Bindung wird über das Backtracking wieder zurückgenommen und durch die Alternative ersetzt, ja,
dann füge doch hier hinten nochmal dieses gepunktete Paar ein.
Und dieses gepunktete Paar ist jetzt die Liste C,
die die einer listet, die nur noch C enthält.
Die kommt hier hinten rein. Okay,
das kann ich natürlich bis ins unendliche Fort, nein,
Quatsch, ich bin ja am Ende angekommen.
Geht natürlich nicht fortzuführen. Ich kann es umgekehrt aufrufen,
ich kann fragen, zu welchen Listen ist die Liste AB,
ein Prefix und dann kriege ich eben das Ergebnis,
ja, es ist die Liste,
die mit AB beginnt und danach irgendwie fortgesetzt wird,
aber dieses irgendwie fortgesetzt kenne ich
noch nicht
Das ist genau die Form der Erzwischenergebnisstrukturen, die ich hier gebraucht habe.
Wo man auch wieder sieht, wie dieses von außen nach
innen aufbauen passiert. Ich kann jetzt Prefix und Welt,
nein, in diesem kann Prefix benutzen,
um mit Hilfe der Prefixdefinition einen Sublistenprädikat zu definieren.
Und zwar ganz einfach dadurch, dass ich sage,
eine Teilliste einer Liste sind die Prefixe dieser Liste.
Und ich kann das jetzt rekursiv abarbeiten,
indem ich jeweils das für die verfügbaren Restlisten mache
Und habe dann praktisch Präfixe von Restlisten. Und das ist miteinander kombiniert,
sind alle Teillisten einer Liste. Also im ersten Falle würde
jetzt das aufgerufen werden mit einer instanzierten Liste. Wir wissen,
Präfix liefert uns als erstes die leere Liste als Präfix dieser Liste,
als zweites das A, die Liste, die nur A enthält,
als drittes, die Liste, die AB enthält,
als drittes, die, als viertes, die gesamte Liste ABC.
Wenn wir jetzt den, nach alternativen Frank,
das waren Alternativen, die Präfix produziert hat,
wenn wir jetzt nach alternativen Fragen wir in die zweite Klausel.
Und die zweite Klausel sagt wieder, schmeiße mir das erste
Element weg und gib mir bitte die Sublisten der Restliste.
Und Sublisten der Restliste sind die Präfigste, der um eins verkürzten Liste.
Und da ist, das ist jetzt die Liste BC,
und die hat als Suffix Lehreliste BBC.
Und wenn ich jetzt wieder nach Alternativen frage, wird die Liste
wieder um eins verkürzt, ihr hält jetzt nur noch C.
Und ich kriege die, oder ich möchte die Präfixe dieser Liste C haben,
und das ist wieder die Lehreliste, die einer Liste C.
Und wenn ich wieder nach Alternativen frage, kriege ich die Lehreliste,
rufe das Ganze für die Lehreliste auf und kriege das Prefix der leeren Liste.
Und da müssen wir uns jetzt angucken,
was sagt Prefix eigentlich zum Prefix der leeren Liste?
Das akzeptiert das. Die leere Liste ist Prefix der,
jeder beliebigen Liste, also auch der Lehrenliste. Das heißt,
über diesen Rekutionsabschluss kriegen wir noch das letzte Ergebnis,
das nochmal, und das ist jetzt der unschöne Effekt,
dass nochmal die Lehreliste Subliste der gegebenen lässt der ABC ist.
Das ist also jetzt ein kosmetischer Und Schönheit,
wir kriegen hier Mehrfachergebnisse, die Lehreliste taucht hier,
hier, hier und hier auf.
Ansonsten kriegen wir keine Doppelergebnisse. Die anderen sehen alle sehr schön aus.
Aber wir haben eben jetzt hier viermal die leere Liste bekommen
und das ist nicht so ganz das, was wir wollen.
Wie könnten wir es verhindern? Gut. Was ist die Ursache dafür,
dass wir viermal die leere Liste als Ergebnis bekommen?
Und dann, daran schließt sich an,
wie könnte man es gegebenenfalls beseitigen
Sie müssen nicht die ganze Zeit den Arm hochhaben.
Ich möchte ihm die Leigenheit geben, nachzudenken darüber.
Und bitte signalisieren Sie, wenn Sie eine Idee haben.
Sie können auch die Aussage verweigern, welche Idee sie haben.
Aber ich möchte, dass Sie mal nachdenken.
Woher kommen die vier leeren Listen als Ergebnis
Diese lange genug auf die Definition gestartet haben,
geben wir noch mal einen Augenblick zurück und gucken uns die Prefix-Definition an.
Und das Verhalten des Prefix-Aufrufes. Ja, der ist ja
immer eingebettet. Wir haben ja im Sublisten Prädikat nichts anderes,
als dass wir immer wieder um die um eins verkürzte Liste Prefix aufrufen.
Und dann ist der erste Teil ja diese vier Ergebnisse,
ist vollkommen identisch zu diesem, na,
zu diesen vier Ergebnissen,
die wir hier hatten
Dann verkürzen wir die Liste um eins und dann kriegen wir
natürlich nicht mehr vier Ergebnisse, sondern wir kriegen drei Ergebnisse.
Dann verkürzen wir das wieder, dann kriegen wir nicht mehr
drei Ergebnisse, sondern noch zwei Ergebnisse.
Und dann verkürzen wir es nochmal, wir kriegen noch ein Ergebnis.
Also woher kommen die vielen leeren Listen? Ja, dann
legen Sie mal aus. Ja, wahrscheinlich aus dem Griffisbrief
. Ja, genau, natürlich. Das Briefix erzeugt als
Prefix jeder Liste, eine leere Liste.
Und da ich das viermal aufrufe, kriege ich vier
leere Listen als Prefix
Das ist die Ursache. Jetzt also der zweite Schritt,
wie können wir es verhindern? Ja, mein Gedanke war,
erstens würde ich eine, also durch zwei Dinge erstens,
ein zusätzlich, ein zusätzliches Fakt, dass uns einmal die Lehreliste generieren würde.
Und zwar ist dadurch, dass wir bei dem ersten Fakt
des deutschen Versuches spät nicht einfach sagen, irgendeine Liste,
sondern dort bereits ein erstes Element voraussetzen, dass wir etwas haben.
Gut, also Ich drehe es mal um,
wir nehmen den zweiten Teil zuerst, ich werde die zusätzlichen Prefix-Dinger los,
indem ich entweder die Prefix-Definition ändere,
dass sie die leere Liste nicht mehr produziert, dann sind sie weg.
Und dann brauche ich den zweiten Schritt,
jetzt habe ich gar kein leeres Element mehr,
gar keine leere Liste als Ergebnis mehr, wenn ich sie haben will,
muss ich einen zusätzlichen Fakt einbauen, der sie erzeugt.
Das sind die beiden Schritte. Und wie ich jetzt
die bösen Geister loswerde, da habe ich natürlich verschieden.
Ich war es nicht. Da habe ich verschiedene Möglichkeiten,
Ich, wir sollten ein bisschen schneller machen.
Der Beamer gibt gleich seinen Geist auf. Ich kann entweder
die Prefix-Definition verändern. Was müsste ich an der Prefix-Definition verändern,
dass die Lehreliste nicht mehr berücksichtigt wird? Statt des ersten Fakts,
dass die Lehreliste, denke ich, jeder Liste,
jetzt müsste man ändern, wie Disziplin,
so eine eine eventige Liste ist, prefixive Liste,
die... Ja,
also so etwas wie
Ja, und dann sehen wir wieder die schöne Wirkung,
wenn ich solche Bedingungen gleich in den Klausel Kopf schreibe,
wenn ich denn eine Einerliste habe,
dann ist die Prefix von jeder Liste,
die mindestens dieses Element enthält.
Und dann ist die leere Liste draußen. Das wäre eine Möglichkeit,
wir können natürlich Kosmetik betreiben,
indem wir erstmal das so lassen und hier an der Stelle korrigieren,
also wie müssten wir die Sublistendefinition verändern,
dass die leeren Listen von Prefix rausfallen
Hm Immer reiner nur, das ist mir zu wenig.
Wir sind doch hier zum Mitdenken und nicht nur zum Zeitabsitzen.
Das sind nur wirklich kleine Modifikationen, die man machen kann.
Also was muss man verhindern
Wir wollen verhindern, dass der Briefix-Aufruf auch eine leere Liste zurückbringt.
Und da sagt man es mal, irgendwie verhindern,
dass ob der zweiten Argumentstelle von Prefix eine leere Liste auftauchen kann.
Sorry, auf der ersten, das Prefix ist die erste Argumentstelle.
Jetzt bin ich schon selber durcheinander. Das S ist das Prefix.
Das muss verhindert werden, dass das eine leere Liste sein kann.
Einen Trick haben wir gesehen, dass es eben hier ein Pattern,
was uns einfach was erzwingt, dass dort keine leere Liste auftauchen kann.
Was mir jetzt einfallen würde, ist,
dass man einfach sagt, dem Oberen, also dem Repräsionsabschluss,
dass es nicht die Lehre ist zu sagen hat.
Genau, das ist die Alternative dazu. Ja,
dass man eine zusätzliche Bedingung einbaut, die eben sagt,
und damit hat man es auch ausgeschlossen.
Ja. Da würden sie ein bisschen wieder direkt nehmen, Cloudle kommen.
Ja, genau, das ist, wie bei Bereding, genau.
Das ist der Trick hier. Der Trick übernommen auf dieses
Prädikat funktioniert dort genauso. Also es gibt verschiedene Möglichkeiten,
wie man es machen kann. Entweder positiv,
indem man sagt, ich möchte,
dass an dieser Stelle nur einer, mindestens einer Listen,
nein, genau einer Listen auftauchen statt Lehrerlisten,
oder die andere Variante ist, ich schließe die Lehrenlisten explizit aus,
indem ich es verbiete.
Der mit der Negation ist immer so eine Sache,
in diesem Falle geht es glatt, aber da sollte man vorsichtig sein,
das könnte auch ein Grund sein,
weshalb man möglicherweise die Richtungsunabhängigkeit beschädigt.
Gut, also wir sehen hier,
wir können durchaus durch Recyceln dieses Präfixes jetzt die Subliste definieren,
ist ihnen an der Definition
noch was aufgefallen
Das ist ein bisschen nichts. Ja,
sie ist endrekrusiv, das ist der letzte Aufruf,
das letzte Teilziel der Klausel ist der rekrusive Aufruf,
das ist das eine.
Ich meine jetzt im Hinblick auf Analogien zu den Prädikaten,
die wir bisher schon gesehen haben, für die Listenverarbeitung.
Die zweite Klausel haben Sie
schon mal gesehen,
und zwar wo
Genial. Der genauer, der wird in ähnlicher Folge,
zum Beispiel auch wenn man kennt,
oder da kennen wir das, was wir vorne haben.
Ja, da wollen wir es ja verwenden.
Hier schmeißen wir es weg. Bei was? Ja,
aber das ist noch gar nicht so lange her.
Da steht sie. Das ist das gleiche Pattern.
Wir gehen die Liste rekursiv durch.
Wir schmeißen das erste Element weg und betrachten die Restliste.
Das, was hier für das Subwix genommen worden ist,
das machen wir jetzt für die Subliste. Was heißt,
wir haben ein Stück von der Suffix-Semantik recycelt für die Sublisten
Semantik und sollten deshalb uns nicht wundern,
dass wir Subliste genauso gut uns aus Prefix und Suffix zusammensetzen können.
Denn das Suffix ist nichts anderes als ein Teil der Semantik,
die wir eben bei Supplis reingesteckt haben. Auch das ist etwas,
was ich möchte, was sie irgendwann mal sehen sollten.
Natürlich muss man sich erst mal als Unglück gewöhnen,
aber wir hatten ähnliche Analogiebetrachtung auch schon.
Hier war die Definition des ersten Elementes einer Liste und dieses Stück Semantik
geht eins zu eins völlig unverändert die Definitionen, dass man immer Prädikats ein.
Da ist die Klausel exakt wieder die gleiche.
Und solche Analogien sollten sie sehen und dann erkennen können,
ja, haha, das ist genau das Gleiche,
deklarative Muster, was wir dort verwenden.
Und das passiert jetzt hier bei dem Verhältnis zwischen Subliste und so fix,
so wie wir das jedenfalls hier definiert haben.
Dass wir eben genau diesen Rekursionsschritt hier für Suffix hier nochmal
das Rad neu erfunden haben für die Subliste.
Und wir könnten jetzt genauso gut die Suffix-Definitionen nehmen und hier mit
einbauen Und da an der zweiten und dritten Stelle steht es,
wir können die Subliste definieren,
als Sufix eines Präfixes oder als Präfix eines Sufixes.
Also gib mir alle Präfixe und nimm davon wieder alle so fixe
oder gib mir alle so fixe und nimm davon wieder alle Präfixe.
Das sind diese verschiedenen Varianten. Wir können das Briefix auch zurückführen und
das ist die Ähnlichkeit, die Sie eben angesprochen haben, auf das Appent.
Beim Appent haben wir auch immer ein Element abgetrennt,
aber wir haben es nicht weggeworfen.
Jetzt werfen wir es tatsächlich weg. Wir sagen also Sufix beispielsweise ist,
Kette irgendetwas mit dem Sofix und dann soll die Liste rauskommen.
Das heißt, ich schmeiße die Elemente hier vorne weg,
sie interessieren mich nicht weiter.
Ich kann also das so fix auf das Appenden zurückführen.
Und dann können sie die Klauseln nebeneinander halten und sie sehen an den Klauseln,
genau welche semantische Modifikationen haben sie gemacht, die das ermöglicht.
Und ich kann es genauso gut umgekehrt machen,
dass ich sage, ein Prefix ist etwas,
was ich mit irgendetwas anderem verketten kann, sodass es die gewünschte Liste ergibt.
Also das Anfangsstück einer Liste. Und dann können sie wieder daneben halten,
die Prefix-Definitionen mit der Appendefinition und können sehen,
welche Analogien und welche Unterschiede gibt es da dazwischen
Und das Spiel kann man jetzt nach Belieben fortführen.
Man kann also Subliste als zweimal appent definieren.
Denn das hier ist nichts anderes als Prefix.
Und das hier ist nichts anderes als Subfix.
Da ich hier Prefix mit Suffix kombiniert habe,
kann ich natürlich das hier einsetzen und das hier einsetzen.
Oder umgekehrt, dass hier das dort einsetzen und das da einsetzen,
beide Richtungsvarianten. Hier oben hatten wir ja auch beide Richtungsvarianten,
Prefix von Suffix oder so Fix von Prefix.
Das heißt, es ist eine ganze Familie von Prädikatsdefinition,
die ich nach Belieben miteinander kombinieren kann.
Und zwar dank der Richtungsunabhängigkeit. Jetzt dämmert so langsam auf
, weshalb ja diesen ganzen Zirkus betreiben
Wir sind jetzt hier in der Lage, in einem
etwas anderen Baukasten als in der Funk zu deinen Programmierung.
Da haben wir auch einen Baukasten,
mit dem wir Funktionen aus Funktionen generieren können.
Und hier spielen wir jetzt auf die gleiche Art und Weise
mit Prädikaten. Und die Kombinierbarkeit ist sehr groß,
solange ich garantieren kann, dass die Dinger richtungsunabhängig sind.
Deshalb lege ich immer so großen Wert drauf,
testen in allen Instanzierungsvarianten. Denn das ist die Voraussetzung dafür,
dass sich solches Recyclinie machen kann.
Wenn das erpennt für nicht richtungsunabhängig funktioniert,
dann wäre ich große Probleme bei dem Sublisten-Dings bekommen
Ich habe früher also immer Multiplikationsaufgaben oder Divisionsaufgaben bei der PANO-Arhythmetik gemacht.
Ich bin davon abgekommen, weil die Terminierungsprobleme nicht überschaubar sind.
Wir hatten dort festgestellt, da muss man genau aufpassen,
dass man eben auch eindeutige Ergebnisse nicht wieder ins Backtracking schickt und Alternativen abfragt.
Und wenn man das in verschiedenen Prädikaten miteinander kombiniert,
das ist sehr trickreich und schwer zu überschauen.
Aber hier sind wir auf der sicheren Seite.
Wir haben bisher noch keine Einzelstelle gesehen, wo wir,
solange wir reine Listenverarbeitung betreiben, ein Terminierungsproblem bekommen.
Das ist bisher vollständig deklarativ und richtungsunabhängig
Gut, ja, das kann man also jetzt nach Belieben
weiter betreiben, indem man das unnötig kompliziert macht.
Das ist also keine Verbesserung mehr, das wird nur noch zunehmend komplexer.
Man kann auch das Allererste hier zum Beispiel Member auf Subliste zurückführen.
Also das Memberprädikat ist dann eben die eine Sub,
das einzige Element der einer Subliste von L. Und auch da kann man sich wieder fragen,
wo ist eigentlich diese Semantik von Member in Subliste geblieben?
Das ist auch wieder,
wo man die beiden Definitionen nebeneinander legen kann und sagen kann,
ah, es ist diese Klausel,
die dafür sorgt,
dass das gleiche Verhalten erzeugt wird
Und dann ist es kein Wunder,
dass sich das eine auf das andere zurückführen kann. Gut,
also das ist so ein bisschen ein schöner Aspekt in der Programmierung,
wenn ich in der Lage bin, wirklich ohne Restriktionen,
Elemente miteinander zu kombinieren und jeweils die verschiedenen Varianten tatsächlich dann auch entsprechend lauffähig sind.
Jetzt gucken wir uns mal einen kleinen Problemfall an,
das Reverse. Reverse hat also eine Liste umdrehen,
sodass die Elemente in umgekehrter Reihenfolge in der Liste enthalten sind.
Was wir haben wollen, ist,
wir schicken eine Liste da rein und die kommt in umgekehrter
Reihenfolge wieder raus
Die leere Liste ist natürlich wieder die leere Liste,
die einer Liste, da ändert sich auch nichts aus der Liste AB,
soll die Liste B.A. werden und aus der Liste ABC soll die Liste CBA werden.
Wir starren wieder, wie wir das bisher immer gemacht haben,
auf diese Beispiele und versuchen mit unserem Regulsionspattern irgendeine Gemeinsamkeit zu
entdecken und wir verzweifeln nach gewisser Zeit, wir sehen keiner.
Also wir trennen hier beispielsweise den Kopf ab und trennen dort
den Kopf ab und stellen fest,
der hier abgetrennte Kopf hat mit dem dort nichts zu tun.
Und das Gleiche können wir
hier machen
Was immer wir durch Kopf- und Restliste erlidien können,
es führt uns nicht auf die gewünschte, umgedrehte Liste.
Und da müssen wir uns fragen, woran liegt es eigentlich?
Wir reformulieren das Problem ein bisschen, indem wir Teilergebnisse uns mal betrachten.
Also, umdrehen einer Liste ist ja nichts anderes,
sie haben einen Stapel und die einzige Operation,
die sie machen können, ist,
sie nehmen das oberste Element aus dem Stapel weg und setzen es auf den neuen Stapel.
Und wenn sie das mit allen Elementen, diese Stapel jetzt gemacht haben,
sind die Elemente auf dem zweiten Stapel in umgekehrter Reihenfolge aufgetragen.
Und das nehmen wir hier mal, legen wir zugrunde.
Anfangs haben wir hier diese Liste,
der Stapel ist leer
Eben das erste Element dieser Liste und packen es auf den Stapel.
Dann haben wir hier noch zwei Elemente und hier ist eins da.
Wir nehmen das nächste Element vom Stapel und packen es auf
den zweiten Stapel und wir nehmen das dritte Element vom Stapel
und haben das Ding umgedreht.
Wir haben hier einen rekursiven Abstieg gemacht,
aber jetzt sind wir am Rekursionsabschluss angekommen und wir haben ein Ergebnis,
mit dem wir nichts anfangen können.
Das ist schlecht, denn wir wollen das Ergebnis nach außen kommunizieren.
Wir können an keiner Stelle jetzt sagen, wir haben das Ergebnis,
hier ist es, wir wollen es zur Verfügung stehen.
Wir brauchen, damit wir das Ergebnis nach außen mitteilen können.
Wir machen ja nicht funktionale Programmierung,
wir machen relationale Programmierung
Wir brauchen eine dritte Argumentstelle,
auf der wir dieses Ergebnis nach draußen kommunizieren können.
Und diese dritte Argumentstelle brauchen wir jetzt ein,
der Prozess, der das macht, heißt Augmentation.
Wir nehmen ein zusätzliches Argument für das Endergebnis.
Es ist sehr ähnlich der Verwendung eines Akkumulators,
weil es ist eigentlich ein Akkumulator, den wir haben.
Der zweite Stapel ist unser Akkumulator, auf dem sammeln wir Ergebnisse auf,
indem wir immer ein Element abtrennen und auf diesen Stapel packen.
Und wenn wir am Rekursionsabschluss angekommen sind,
dann können wir diesen Stapel nehmen und per Co-Referenz
auf die dritte Argumentstelle übergeben und damit nach außen kommunizieren
Das ist der Trick hier dabei. Also wir brauchen
um das effizient machen zu können. Es gibt auch noch
eine naive Variante davon, die nicht so effizient ist.
Wenn wir das effizient machen wollen, brauchen wir ein zusätzliches Argument,
die eigentlich zweistellige Relation zwischen einem Stapel und seiner umgedrehten Variante
wird zu einer dreistelligen Relation zwischen einem Stapel,
einem umgedrehten Stapel und einer Ergebnisposition, die nur der Kommunikation nach außen dient.
Und daraus machen wir jetzt mal eine prädikatsdefinition.
Wenn denn der erste Stapel leer ist,
dann enthält der zweite
das gewünschte Ergebnis
Dann, das ist die Liste in umgedrehter Form.
Und die wird per Co-Referenz auf die dritte Argumentstelle übergeben und nach außen kommuniziert.
Ansonsten nehmen wir immer das erste Element von diesem Stapel und
packen es auf den zweiten Stapel drauf und rufen das Ganze mit dem reduzierten Stapel rekursiv auf.
Und das Ergebnis, was wir hier am Rekursionsabschluss übergeben,
das wird einfach immer nur durchgereicht. Das ist also in dieser Tabelle hier,
kann man das sehen, wir haben hier unten das Ergebnis,
wir wollen, dass das Ergebnis auch dort auftaucht,
wir wollen, dass es auch dort auftaucht.
Und wir wollen auch,
dass es schließlich ganz oben auf der obersten Rekursionsebene auftaucht,
sodass wir hier dann wissen, wenn wir ABC umdrehen wollen,
dann steht hier hinten das Ergebnis und wir müssen das Ganze initialisieren mit einem leeren Stapel,
also dieser Akkumulator, vaiable, wird mit einer leeren Liste initialisiert.
Und das Ganze wollen wir dann natürlich wieder abbilden auf eine zweistellige Relation,
der Implementierungstrick mit dem Akkumulator soll nach außen möglichst nicht sichtbar sein,
deshalb verstecken wir es in so einem kleinen Rapper und sagen,
wir drehen eine Liste um zu ihrer umgekehrten Variante,
indem wir das Reverse 2 aufrufen,
das ist dieses Prädikat hier oben und initialisiert mit einer
leeren Liste als zweiter Umdrehstapel
Und dann kamen wir eben dieses A für Akkumulator.
Hier ist mit der leeren Liste initialisiert. Wir nehmen das
erste Element vom Stapel und packen es auf die leere Liste drauf.
Und mit diesem reduzierten Stapel und diesem erweiterten Stapel rufen wir das Ganze rekursiv auf,
bis wir da hinten ankommen und dann wird nur noch das Ergebnis immer nach oben übergeben.
Oder mit anderen Worten, da wird nichts übergeben.
Wir haben beim Rekursiven Abstieg bereits eine Choreferenzkette aufgebaut.
Von der obersten Ebene referieren wir immer nur wieder auf die
eingebettete Ebene und so weiter,
bis wir unten am Rekursionsabschluss ankommen,
wo das Ergebnis zur Verfügung steht
Gut, was wir hier jetzt an Rekursionsschema haben,
wir rufen das Ganze auf mit einer Liste,
einem uninstanziierten Stapel und wollen das Ergebnis sehen,
wir verkürzen die erste Liste um eins,
nehmen den Kopf als erstes Element auf die zweite Argumentliste drauf und auf der dritten Argument-Position wird nur diese Co-Referenz festgestellt,
was immer der Wert von X1 sein wird,
es ist auch der Web von X. Wir machen den zweiten Schritt,
wir trennen das B ab und packen es auf den zweiten Stapel drauf und haben Wissen,
was immer das Ergebnis des Umdrehens des weiteren Umdrehens,
muss man sagen, das ist ja ein Zwischenergebnis,
weiteren Umdrehens dieser Liste ist,
es wird der Wert von X2 sein.
Und der Wert von X2 ist gleich X1 und der von X1
ist gleich X. Und im dritten Fall nehmen wir wieder
ein Element runter, packen es auf den zweiten Stapel.
Jetzt kommt der Rekursionsabschluss zum Tragen. Hier ist die Lehreliste
da. Auf dem zweiten Stapel steht das Ergebnis.
Das wird per Co-Referenz auf die dritte Argumentstelle übergeben.
Und dann wissen wir, der Wert von X3 ist gleich
X3, von X2 gleich X1, von X1 gleich 1.
Das Ganze ist wieder endrekursiv und der rekursive Aufstieg ist eigentlich nicht nötig.
Und wir sehen es auch an der Definition des Ganzen,
Der rekursive Aufruf von Ref 2 ist das letzte Teilziel in der Klausel,
ist also eine entrekusive Definition.
Das ist also kein überraschendes Verhalten. Weitere Beispiele,
das sind jetzt eingebaute Prädikate, die in Prolock so auch zur Verfügung stehen.
Wir gucken hier sozusagen, das ist ein beliebtes Mittel,
das wir auf diese Mantik dieser eingebauten Prädikate schauen und von dort aus verstehen,
wie sie tatsächlich funktionieren.
Hoffentlich habe ich jetzt nicht zu viel gesagt,
ist das wirklich eingebaut
Ja. Nein, leider nein. Ach gut,
dann ist es hier nicht eingebaut.
Dann gucken wir uns die Bedeutung so an. Also,
die Lead-Element soll jeweils alle Vorkommen eines Elements aus der Liste löschen.
Also Beispiel Aufruf ist hier unten angegeben. Wir haben eine
längere Liste mit mehreren Vorkommen von A gegeben und wir möchten,
dass alle vorkommen von A gelöscht werden,
sodass das die gewünschte Ergebnisliste ist
Idee ist, ja, wir gehen die Liste rekursiv durch,
wir testen immer, ob das erste Element der Liste,
das zu entfernende Element ist.
Und wenn es das zu entfernende Element ist,
kommt es nicht in die Ergebnisliste rein,
wenn es nicht das zu entfernende Element ist,
kommt es in die Ergebnisliste rein.
Und das kann man hier nachvollziehen, wenn die abzubauende Liste leer ist,
dann ist auch die Ergebnisliste leer, egal welches Element entfernt werden sollte.
Wenn denn das erste Element der abzubauenden Liste gleich dem zu entfernenden Element ist,
diese Chorreferenz von E nach E ist das Entscheidende hier,
dann wird die Ergebnisliste verwendet,
ohne dass dieses Element weiterverwendet wird.
Also das E fällt unter dem Tisch
Wir müssen natürlich dafür sorgen,
dass die aus der Restliste R auch die Elemente entfernt werden.
Und das passiert durch den Körper der Klausel rekursiv.
Also wir rufen wieder, das zu entfernen,
der Element mit der Restliste auf und kriegen dann diese Bearbeitete, diese reduzierte Restliste.
Und die wird einfach wieder nur nach oben durchgereicht,
wie wir das eben beim Reverse auch hatten,
ohne dass noch irgendetwas damit passiert. Und im alternativen Fall dann,
wenn denn das erste Element dieser abzubauenden Liste nicht gleich dem zu entfernenden ist,
dass es diese Nicht-Udifikationsbedingung hier, dann muss es in die Ergebnisliste übernommen werden.
Dann wird jetzt hier auf der Ergebnisliste ein ein, eine erweiterte Liste konstruiert, die dieses erste Element als erstes Element enthält. Gut,
also die zwei Fälle, das Ding ist nicht,
muss nicht entfernt werden, das ist hier unten,
es ist nicht das zu entfernende Element,
dann kommt es in die Ergebnisliste, hier ist es,
das zu entfernende Element, deshalb wird die Ergebnisliste unverändert hier übernommen,
aus dem rekursiven Abstieg, das heißt,
das Element eh bleibt dabei unberücksichtigt und das Ganze terminiert,
wenn denn die Liste leer ist,
dann ist auch die Ergebnisliste leer.
Und auch da sieht man wieder,
die Ergebnisliste wird durch Unifikationen von außen nach innen aufgebaut.
Ich kenne für das erste nicht zu entfernende Element,
kenne ich das erste Element der Ergebnisliste. Alles,
was ich noch nicht kenne, ist diese Restliste hier,
die muss über die Rekussion erst noch berechnet werden.
Die einzig sinnvolle Verwendung, die ich hier kenne,
ist diese, es funktioniert auch in anderen Aufrufvarianten,
aber das macht keinen rechten Sinn dabei.
Es gibt eine zweite Variante davon, das heißt, ein Prädikat,
was nicht alle vorkommen, eines Elementes entfernt, sondern immer nur eins.
Und da, wenn ein Element mehrfach drin vorkommt,
dann führt das eben zu mehrfachergebnissen, wo jeweils ein Vorkommen beseitigt worden ist.
Also man sieht es hier unten an dem Aufruf hier sehr schön.
Ich soll wieder das Element A aus dieser Liste entfernen.
Und das erste Ergebnis hat eben das erste Vorkommen dieses Elements entfernt.
Wenn ich nach, die anderen beiden sind noch da,
das hier und das da, wenn ich nach Alternativen frage,
dann wird das zweite Vorkommen aus der Liste entfernt,
das erste und das dritte sind noch da.
Und wenn ich wieder nach Alternativen frage, wird das dritte Vorkommen entfernt,
das erste und das zweite sind, ist noch da.
Das ist das gewünschte Verhalten, was wir hier haben.
Und ich kann es einfach dadurch erreichen, indem ich sage,
ja, wenn denn das erste Element der Liste,
das zu entfernende Element ist,
dann ergibt die Restliste als Ergebnis aus
Das ist jetzt der feine Unterschied. Jetzt kann man diese
beiden Definitionen wieder nebeneinander halten und kann sich fragen, was ist eigentlich gleich?
Was ist der Unterschied? Hier habe ich auch geguckt,
ob das erste Element das Gewünschte der Restliste ist.
Aber dann habe ich das nicht als Ergebnis ausgegeben,
sondern habe es in die Rekussion geschickt.
Und habe gesagt, dann entferne bitte auch aus dem Rest
das Element. Jetzt sage ich, ich habe es geschafft,
es ist vollbracht, ich habe das Element gefunden und ich weiß,
die Restliste ist das gewünschte Ergebnis.
Das erzeugt mir nämlich genau dieses Ergebnis hier.
Das erste Element der Liste ist das gewünschte Ergebnis,
also ist die Restliste, die mit F beginnt,
die das erste mögliche Ergebnis
Wenn es in allen anderen Fällen,
egal ob das erste Element jetzt das Gewünschte oder das Nicht-Gewünschte ist,
in allen anderen Fällen mache ich die Rekursion,
indem ich also ein Element aus der Liste entferne,
in die Ergebnisliste übernehme und die Restliste diesem Element entfernen unterwerfe.
Und dadurch wird jetzt das zweite, das dritte und das vierte Vorkommen des gewünschten Elementes aus der Liste entfernen.
Das hat zwei sinnvolle Verwendungsweisen. Das eine ist eben,
gib mir nacheinander die Listen, die übrig bleiben,
wenn ich ein Element daraus entferne,
ein Vorkommen eines Elementes daraus entferne, die zweite Verwendungsweise,
die auch manchmal ganz sinnvoll ist, ist,
entferne mir ein Element aus der Liste und gib mir die restliche,
die übrigbleibende Liste zurück.
Gib mir die Liste aller Elemente zurück, die dann noch übrig bleiben.
Also entferne A aus der Liste und was übrig bleibt, ist die Liste BC.
Entferne B aus der Liste und die Liste AC bleibt übrig.
Entferne C aus dieser Liste und die Liste A.B. bleibt übrig.
Dafür gibt es auch tatsächlich ganz sinnvolle Verwendungsweisen. Also,
dies auch ein kleines Lehrstück darin,
dass man immer genau sagen muss, wovon redet man eigentlich.
Oben drüber steht immer löschend von Elementen,
löschen eines Elements, das hört sich
verdammt ähnlich an
Also hier diese Mante ganz genau spezifizieren wegen der, wegen des Lichtdeterminismus,
den wir haben. Wir können Löschen eines Elementes kann bedeuten,
entferne alle vorkommen auf einen Rutsch oder entferne über das Backtracking immer jeweils ein Vorkommen.
Das muss in der, in der Beschreibung des Ganzen tatsächlich explizit ausgewiesen werden.
Sonst kommt es da leicht zur Verwechslung. Ja,
ein Ebene in einer Liste, wir gehen jetzt erstmalig dazu über,
rekursiv eingebettete Listen zu betrachten, die also nicht nur konstanten enthalten.
Ich meine, falsch, das sind auch konstant,
die liegt nur Atome oder Zahlen, also Atome enthalten,
Namen oder Zahlen, sondern die auch wieder Listen als Elemente enthalten.
Also so etwas, was wir schon ganz zu Anfang uns
bei der Ulifikation von Listen mal angeguckt haben, etwas von der Art.
Solche rekursiv eingebetteten Listen sind das jetzt,
die wir betrachten und wir wollen aus so einer rekursiv eingebetteten Struktur eine flache Liste bauen,
die alle Elemente enthält,
die hier in der Liste in der Listenstruktur auch enthalten sind,
aber in einer linearen Liste.
Das ist üblicherweise das, was man als Flatten bezeichnet.
Also das Flachklopfen einer Liste. Gut, also
was müssen wir machen? Wir müssen uns angucken,
wenn wir eine leere Liste haben und die Blatt drücken sollen,
dann ist es wieder die leere Liste.
Ansonsten schauen wir uns immer den Kopf der Liste an und fragen,
ist der Kopf ein Atom?
Also eine Zahl oder ein Name? Zahlen beliebigen Typs natürlich
. Und wenn es das ist, dann müssen wir
nichts machen mit dem Kopf
Dann müssen wir nur dafür sorgen, dass der Körper,
dass die Restliste auch plattgedrückt wird.
Das ist dieser rekursive Aufruf hier. Also,
falls der Kopf bereits atomar ist, dann ist alles in Butter,
falls dort, dann ist alles in Butter,
aber die Restliste muss auch noch bearbeitet werden.
Das ist der einfache Fall, der zweite Fall ist,
wenn der Kopf hier tatsächlich wieder eine Liste ist,
die hier eingebettet ist, also so etwas,
dann muss ich den Kopf plattdrücken und daraus eine lineare Liste machen.
Und ich muss natürlich die Restliste
ebenfalls plattdrücken
Und zum Schluss muss ich das,
was ich im Kopf platt gedrückt habe, mit dem,
was ich in der Restliste platt gedrückt habe, per erpennt verknüpfen.
Also das, was aus dem Kopf flach geklopft ist,
mit dem, aus der Restliste flach geklopft,
wird per erpennt verknüpft und zu der Ergebnisliste gebracht.
Das ist jetzt auch wieder, nein, erstmalig,
in der Übung taucht ihm das auch auf,
aber in dem anderen Zusammenhang, ein Fall von verzweigender Rekursion.
Hier haben wir jetzt erstmalig nicht mehr einen Rekursiven
Aufruf im Körper der Klausel, sondern zwei rekursive Aufrufe.
Nämlich einerseits bearbeitet den Kopf,
andererseits bearbeitet die Restliste
Und das ist ein spezieller Fall von Rekursionen,
wo ich endrikusive Lösungen nicht mehr ohne weiteres annehmen kann.
Dort versagt unser Trick, mache daraus doch mal bitteschön eine Endrekursive Definition,
weil ich danach immer noch dieses Appentier durchführen muss.
Wir werden nach Weihnachten vermutlich den Trick sehen, wie wir
es trotzdem machen können, indem wir nämlich dieses Appent verschwinden lassen.
Aber das ist dann schon das spezielle Kapitel Magie in der Logikprogrammierung.
Da wird es dann richtig spannend. Das ist alles
noch Kinderkram, was wir hier machen
Gut, also die Idee sollte klar sein,
leere Liste, plattgedrückt ist wieder die Lehreliste.
Ansonsten, wenn der Kopf atomar ist,
ist alles in Ordnung, kümmere dich nur um die Restliste.
Wenn denn der Kopf eine Liste ist, dann muss er
plattgedrückt werden und die Restliste muss auch plattgedrückt werden.
Und die Ergebnisse dieser beiden Aufrufe müssen per Appent wieder miteinander verknüpft werden.
Wir können uns jetzt also abschließend einen kleinen Überblick verschaffen über
verschiedene Rekursionsfirmata, die wir bisher gesehen haben.
Wir haben eine große Welt der Rekursion,
die sich in verschiedene kleinere Welten unterteilt,
Insbesondere haben wir uns hauptsächlich mit dem Fall der schlichten Rekursion befasst.
Wir haben Prozesse gesehen, wo wir im absteigenden Ast der Rekursion die Berechnung durchgeführt haben.
Das sind die, all die Endtrikusiven Definitionen,
die wir gesehen haben, wo es einen Rekursionsaufruf gibt.
Und dieser ist das letzte Teilziel im Rekursionsschritt. Und wir haben
andere gesehen, wo wir induktiv im die Berechnung machen,
wo der Abstieg nur dazu dient, den Rekursionsabschluss zu finden.
Das sind die beiden Fälle von schlichter Rekursionen,
die wir haben, die insgesamt zur Klasse der Linie-Ahn-Rekursion gehört.
Das sind alle die, die nur einen rekursiven Aufruf
immer machen
Und es kann natürlich der Fall sein,
dass ich sowohl im Abstieg als auch im Aufstieg Berechnung mache.
Deshalb ist die Klasse der linearen Rekursionsprogramme größer als einfach nur die Vereidigung dieser beiden.
Und wir haben jetzt erstmalig einen Fall gesehen,
der hier draußen liegt, der also nicht mehr linear ist,
sondern der verzweigend ist, wo ich in,
wo die Rekursion einmal in die Richtung absteigen kann und einmal in die Richtung absteigen kann,
beides aber machen muss,
damit das Gesamtergebnis vorliegt.
Und das haben wir hier vorliegen,
wir müssen einerseits den Kopf flach drücken,
wir müssen andererseits die Restliste flach drücken und wir müssen
die Ergebnisse beider Schritte
miteinander kombinieren
Die Aufgabe beim Flatten. Und damit haben wir ein Beispiel dafür gefunden,
wo wir jetzt hier draußen in diesem Bereich der Rekursionen sind.
Und der Trick mit der Endrekursion, der funktioniert immer nur,
solange wir im Bereich der Linie armen Rekursionen sind.
Gut, damit haben wir auch einen Punkt erreicht,
wo wir einen abschließenden Sicht auf die Welt der Rekursion haben.
Wir schauen uns dann nächste Woche diverse Anwendungsfälle für Listenverarbeitung an,
wo wir dann sinnvolle Sachen mitlisten tatsächlich machen können.
Also wo wir nicht mal dafür interessieren, wie funktioniert es
eigentlich, sondern was können wir alles damit erreichen.
Und dann steht sowas wie Sortieren, Suchen und dergleichen im Mittelpunkt.
Und bis dahin wünsche ich Ihnen ein schönes Wochenende und eine
erfreuliche Woche.
Tschüss und schönen Dank