So, einen schönen guten Tag allerseits, nachdem wir uns
das Basisinventarium angeguckt haben, was wir für die Logikpremierung brauchen.
Und das haben wir spätestens mit der Einführung der rekursiven Datenstrukturen
erreicht. Sind wir jetzt dabei Add-ons zu diskutieren?
Wir hatten die letzten beiden Veranstaltungen, die Arrithmetik,
die ein Add-on in der Logikprogrammierung ist, ganz einfach,
weil man den Service des Arrithmetikprozesses in der Maschine
natürlich nutzbar machen will
Damit aber alle Beschränkungen, die der Arratmetikprozessor hat,
in die Logikprogrammierung importiert. Und deshalb ein gesondertes Verhalten hervorruft.
Und wir werden jetzt uns befassen mit Listenverarbeitung.
Das ist also eher der symbolische Teil der Informationsverarbeitung im Gegensatz
zu dem numerischen Teil, den wir gerade behandelt hatten.
Ich hatte ja schon gesagt, die Numerik ist nicht gerade
das Gebiet, wofür man die Logikprämierung einsetzen sollte.
Jetzt kommt also der Bereich, wo tatsächlich die ganzen Stärken ausgespielt werden können.
Und dazu Gucken wir uns als erstes eine so taktische Notationsweise für Listen an,
genauer gesagt muss ich sagen, verkettete Listen,
weil mittlerweile alles mögliche als Listen bezeichnet wird in der Informatik.
Früher war das ein Privileg der verketteten Listen,
als Listen bezeichnet zu werden und wir werden die Vorzüge verketteter Listen gleich im Detail sehen.
Wir müssen dann den Begriff und der Unifikation auf Listen erweitern.
Klar, wir führen ein neues Sonntagskonstrukt ein,
wir müssen dann diese Mandik des Ganzen klären und wir werden sehen,
dass es wirklich nur eine syntaktische Modifikation ist.
Wir werden an der grundlegenden Verarbeitungsweise der Maschine nichts ändern.
Das bleibt alles erhalten
Und wir gucken uns dann diverse Beispiele aus der Welt der Listenverarbeitung an,
weil die Motivation für den ganzen Abschnitt eben ist,
dass sehr viele Probleme in der Informationsverarbeitung auf die Arbeit mit Listen zurückgeführt werden können.
Ja, die Bedeutung hier in diesem Kontext ist,
Listen sind die wichtigste, rekursive Datenstruktur. Sie sind sehr,
sehr flexibel einsetzbar als Container für beliebige Datentypen,
etwa im Gegensatz zu Collections,
die man in anderen Daten in anderen Programmiersprachen hat,
wo der Datentyp der Elemente festgelegt ist,
kann ich ihr in eine Liste belieben
Datentypen unterbringen, auch beliebig, unterschiedliche Datentypen.
Und das Ganze ist nicht längenbegrenzt. Eins steht hier noch
nicht mit dabei, das sollte man auch mit hinzufügen.
Sie sind auch unbegrenzt rekursiv einbettbar. Und von dieser Eigenschaft
werden wir auch Gebrauch machen. Also es gibt eine
ganze Reihe von Vorteilen, die Listen mit sich bringen.
Und weil sie so häufig gebraucht werden,
führen wir also eine spezielle Syntax dafür ein,
damit das Arbeiten mit Listen so bequem wie möglich ist.
Im Weiteren werde ich als erstes die Arbeit mit Linearenlisten
behandeln und gehe davon weitgehend davon aus,
dass sie sowas schon mal über den Weg gelaufen ist.
In Java beispielsweise. Und ich werde das dann später auf verzweigende Listen erweitern,
die wir etwa brauchen, um mit Bäumen umgehen zu können.
Listennotation ist ganz simpel. Wir haben die Eck in Klammern dafür reserviert.
Dies ist ihnen schon begegnet bei der Spezialsyntax für das Consulting von Dateien.
Hier benutzen wir das jetzt also, um Listen darzustellen.
Wir haben eine Ecke öffnende Klammer, eine Ecke schließende Klammer.
Und dazwischen können wir die Elemente der Liste aufzählen.
Durch Kommas getrennt. Wenn die Liste leer ist,
dann gibt es das spezielle, die spezielle Notation,
Klammer auf, Klammer zu, Auch sehr bildlich deutet an,
da ist kein Inhalt vorhanden.
Das ist die iterative Sicht auf Listen.
Ich zähle auf, welche Elemente in der Liste enthalten sind.
Wir können das Ganze aber auch rekursiv sehen.
Es ist eine alternative Sicht auf die gleiche Art von Datenstruktur.
Das ist nämlich die rekursive Sicht. Ich hatte schon gesagt,
die wichtigste rekursive Datenstruktur wäre komisch, wenn man es nicht rekursiv definieren könnte.
Und da haben wir wieder den typischen Fall einer rekursiven Definition,
den wir auch schon in anderen Fällen gesehen haben.
Und das sollten sie sich jetzt mal überlegen,
an welcher Stelle wir schon etwas ganz,
ganz ähnliches gesehen haben
Also, wir definieren die leere Liste als das Anfangselement und sagen,
dies ist eine Liste. Und dann führen wir eine Nachfolgerbeziehung
ein, die sagt, ein geordnetes Paar.
Und dieser Begriff des geordneten Paars ist konstitutiv für verkettete Listen.
Da gibt es geordnetes Paar aus einem Element und einer Liste ist wieder eine Liste.
Ja, also ich brauche irgendwie eine zweistellige Struktur,
die mir sagt, was ist das neu hinzukommende Element?
Was ist die Liste, zu der ich das Element hinzufüge?
Und wir haben wieder den Domänenabschluss, der sagt,
nur was man so bilden kann, ist auch wirklich eine Liste.
Alles andere wird keine Liste sein. Diese rekrusive Sicht
kann man jetzt auch in einer Sonntagsnotation unterbringen
Und die ist sie unten mal angedeutet. Das ist nämlich
der senkrechte Strich. Der endgerechte Strich ist der listenkopfseparator,
der genau ein Element in diesem Falle hier von der Liste
abtrennt und mir die Restliste der übrig gebliebenen Elemente liefert.
Das Ganze ist jetzt natürlich wieder richtungsunabhängig.
Wir sind jetzt im Kernbereich der Logikprogrammierung wieder.
Hier sollte über weite Strecken Richtungsunabhängigkeit erreichbar sein.
Wenn wir nicht irgendwo systematisch was falsch machen,
ist das eigentlich immer anstrebenswert und erreichbar
Aber wir können zum Beispiel die Sicht benutzen,
wir haben eine Liste gegeben,
wir unifizieren es mit diesem Pattern und dann wird diese vajabliche Forn
auf das erste Element der Liste unifiziert und diese vajabliche hinten auf die verbleibende Restliste.
Und das ist genau die Sicht auch als geordnetes Paar.
Dieser senkrechte Strich beschreibt also eingeordnetes Paar aus einem Kopf und
einer Restliste. Das Ganze ist syntaktisch noch erweitert,
ich kann hier vorn beliebig viele Listenköpfe angeben,
ich muss sie allerdings aufzählen, also in diesem Fall kann ich sagen,
hier erstes Element,
zweites Element,
drittes Element und alles,
was dann noch übrig bleibt,
wird an Restliste gebunden
Das ist die, sind die beiden Verwendungsmöglichkeiten, die man hier hat. Gut,
ehe wir uns die Implementierung angucken, haben Sie ein gutes,
ich suche ja immer nach Beispielen im realen Leben,
wo man ähnliche Strukturen findet.
Haben Sie ein gutes Beispiel im realen Leben?
Also vielleicht sollte man erstmal damit anfangen,
wo haben wir eine ähnliche Definition schon mal gesehen?
So eine rekrusive Definition eines speziellen Datentyps.
Es ist jemand aufgefallen
Hm. Ach, das Gedächtnis lässt mit dem Alter nach,
das geht mir auch so.
Die Definition der PANO-Zahlen war genauso geregelt, ja,
über die Nachfolgerbeziehung einer PA-Notzahl haben wir gesagt,
was ist eine PANO-Zahl? Nur der Unterschied ist hier,
dass wir bei der PA-Notzahl immer einfach nur die Nachfolger spezifiziert haben.
Und hier ist die Nachfolgerbeziehung immer eingeordnetes Paar. Und jetzt,
wenn sie sich den nächsten Übungszettel angucken, werden sie auch verstehen,
warum dort gerade Binerbäume behandelt werden.
Denn Binorbäume basieren auch auf einer solchen zweistelligen Struktur in Form
von geordneten Paaren
Gut, also das erste ist, wir erweitern all das,
was wir für PA-No-Zahlen bisher gesehen haben, jetzt auf Listen.
Wir müssen, der einzige Unterschied besteht darin,
während wir bei PA-No-Zahlen mit einer einstelligen Struktur gearbeitet haben,
arbeiten wir jetzt hier mit einer zweistelligen Struktur.
Das bringt neue Möglichkeiten, neue Probleme mit sich,
aber von dieser Analogie können wir durchaus auch an vielen Stellen profitieren.
Da komme ich mehrfach noch darauf zurück. Ja,
jetzt die Frage in der realen Welt,
haben wir irgendwo was, was diesem Konzept einer Liste entspricht?
Irgendein Analogorn, Sie erinnern sich für die PA-No-Zahlen,
hatte ich die Matroschkas mitgebracht.
Was werde ich für die Liste mitbringen
Oder was würden Sie nehmen, um zu veranschaulichen,
wie man, was eine Liste, was einer Liste äquivalent ist?
Die Lage ist nicht ganz so einfach. Hat jemand
eine Idee? Hat keiner eine Idee? Ich habe ihnen
natürlich wieder was mitgebracht, ja. Die Klopapierrolle,
Sie haben bei Frau Dreschler schon funktionale Programmierung gehabt,
die Klopapierrolle ist das Muster, der senkrechte Strich ist,
das Messer sozusagen, was es mir erlaubt,
ein Element von der Liste abzutrennen und ich halte eine Restliste übrig.
Wenn ich dieses Listenpad dann auf eine lineare Liste anwende und zwar nur dann,
Wenn ich es auf eine lineare Liste anwende,
sollten Sie sehen, dass das einen Typunterschied gibt.
Der Kopf der Liste ist ein Element der Liste.
Die Restliste ist immer wieder eine Liste. Und sei es die Lehreliste,
wenn Sie hier hinten die Papprolle in der Hand haben.
Aber da ist ein systematischer Typunterschied dazwischen. Das gilt allerdings nur dann,
wenn Sie es wirklich auf eine lineare Liste anwenden und
wir besprechen oder reden jetzt hauptsächlich erstmal, nein,
vorläufig erstmal nur über Linearelisten.
Und dann sehen wir eben auch,
wir können eben die ersten drei Elemente abtrennen,
die wir dann einzeln zur Verfügung haben und an die
vaiablen Kopf 1 bis Kopf 3 binden können.
Und wir behalten wieder
eine Restliste übrig
Wir können die Unifikation, das sollte auch deutlich werden,
wenn denn unsere Liste, die wir da haben,
nur noch aus zwei Elementen besteht, dann wird keine Unifikation möglich sein.
Also trenne die ersten drei Elemente von dieser Liste ab,
funktioniert nicht. Also auch hier haben wir wieder die Situation,
Unifikation kann gelingen und kann scheitern und wir müssen uns angucken,
unter welchen Bedingungen gelingt sie und unter welchen scheitert sie.
Nachdem wir das Analogan haben, gucken wir jetzt hinter die Kulissen,
was passiert innerhalb der Maschine? Ich habe schon gesagt,
wir brauchen eine zweistellige Struktur, mit der wir Listen aufbauen können,
diese zweistellige Struktur kann man sich in Form dieser Kunstzellen hier veranschaulichen,
die immer zwei Elemente miteinander verbinden.
Im Falle der linearen Liste trage ich hier vorn das Element ein.
Und hier hinten die Restliste. Und dann sehen sie,
dass diese senkrechte Trennwand zwischen den beiden Quadraten
genau wieder dem senkrechten Strich in unserer Listenrotation entspricht.
Die den Kopf der Liste von der Restliste trennt.
Wenn ich denn also über die eine Liste rede,
steht hier hinten als Restliste die leere Liste drin.
Das ist ein vereinbarter Listenende-Marker, der sagt, ich bin die leere Liste.
Das heißt also, wenn immer wir diese leere Liste auf
der Prolog-Ebene hinschreiben, entspricht das intern diesem vereinbarten Listenende-Marker.
Wenn wir denn eine einer Liste haben,
dann ist es genau eine solche Kunstzelle,
die der vorderen Teil den Verweis auf dieses Element enthält und
die im hinteren Teil den Verweis auf die leere Liste enthält.
Und wenn wir denn eine zweielementige Liste haben, dann sind es zwei Kunstzellen,
wo die erste im ersten Teil auf das erste Element verweist,
die zweite im ersten Teil auf das zweite Element verweist und wo
das zweite Kästchen des ersten Elementes auf die Restliste verweist,
die nur noch A enthält.
Und das kann ich jetzt beliebig fortsetzen,
indem ich von mir solche Elemente hinzufüge.
Was hier deutlich wird, ist,
dass diese Listen
eine Zugriffsbeschränkung enthalten
Sowohl zum Konstruieren der Liste als auch zum Extrahieren von Elementen
kann ich immer nur vom Listen anfangen zugreifen.
Also ich kann am Listen anfangen ein neues Element davor setzen.
Das ist problemlos möglich. Ich kann auch das erste Element abfragen.
Ich kann auch das erste Element entfernen. Wenn ich den
hinteren Bereich der Liste anfassen will, muss ich mehr Aufwand treiben.
Da muss ich nämlich die gesamte Liste umorganisieren,
sonst stimmt das nicht mehr.
Also ich kann nicht einfach ein Element hier aus der Liste entfernen.
Das ist mindestens damit verbunden, dass ich diesen Zeiger dann
hier nach dort umbiege
Und das kann möglicherweise Nebeneffekte an anderen Stellen erzeugen,
wenn denn diese Liste von mehreren, bajablen beispielsweise gebunden wirst.
Dann ändert man Harp die vajablen Belegungen. Das ist ein
Nebeneffekt, den wollen wir in der Logikprogrammierung nicht haben.
Wir wollen neben effektfrei erstmal programmieren und Nebeneffekte nur unter ganz
kontrollierten Bedingungen akzeptieren. Deshalb gilt für uns erstmal der Grundsatz,
Zugriff zur Liste immer vom Listenkopf her.
Niemals vom Listenende. Und alles andere müssen wir
uns überlegen, wie wir damit klarkommen
Und diese beiden Repräsentationen, die wir jetzt hatten,
einerseits die Iterative Aufzählung der Elemente mit Komma getrennt und andererseits diese Darstellung mit dem senkrechten Strich,
sind zwei Möglichkeiten ein und dieselbe Liste darzustellen.
Also wenn ich denn sage,
ich habe hier eine Kunstzelle mit B drin und einer Restliste,
die als erstes Element in der Kunstzelle L enthält und einer Restliste,
die als erstes Element ein A enthält und dann den Listenende Marke hat,
dann ist das genau die Liste, die B-L-A enthält.
Also diese beiden Darstellungsweisen sind vollständig Synonymen. Es gibt in der Maschine
keinen Unterschied, das eine von dem anderen zu unterscheiden.
Wenn ich eine Liste ausgebe,
werde ich immer diese Notation bekommen und niemals diese
Ja, aber ich kann diese im Input immer verwenden.
Ich kann im Input auch die tatsächlich interne Thermstruktur verwenden und weil die Dinger gepunktetes Paar heißen,
weil in den frühen Liz-Dialekten,
also den Vorgängern von Scheme in der funktionalen Programmierung,
weil die dort immer dortet Pear hießen, also gepunktetes Paar,
ist auch in Prolog dieser Punkt hier, der Funktor für die Listendarstellung.
Sie können also die gleiche Liste BLA auch in dieser Form eingeben. Heißt,
ich bin eine zweistellige Punktstruktur mit B an der ersten Stelle
und einer Punktstruktur an der zweiten Stelle und die Punktstruktur enthält
L an der ersten Stelle und diese Punktstruktur an der zweiten
Stelle und die entweder wieder A und den Listen Ende Marker
Ja, ich hatte es schon angedeutet,
das Ganze ist nicht beschränkt auf solche linearen Listen,
die einfach nur durch Elementaufzählung beschrieben werden können,
sondern ich kann an jeder Stelle in der Liste auch wieder rekursiv Listen einbauen.
Also so etwas ist ohne weiteres möglich,
dass ich eben sage, hier,
das zweite Element dieser Liste ist eben eine,
wieder rekursiv eingebettete Liste und das zweite Element dieser rekursiv eingebetteten Liste ist wieder eine Liste.
Und das kann ich unbegrenzt weiter fortführen.
Natürlich modulo des Speicherplatzes, der dafür gebraucht wird.
Aber es gibt keine systembedingten Einschränkungen, die sagen,
200 Mal darfst du das machen und danach
nicht mehr
Das ist also unbeschränkt, genauso wie die PA-No-Zahlen nach oben unbeschränkt sind.
Da gibt es keine eingebauten Obergrenzen. Ich möchte es
hier nur erwähnen, es ist im Augenblick nicht zentral.
Ich will es nur der Vollständigkeit halber erwähnen. Wir haben
bis jetzt immer über Listenstrukturen gesprochen, die wirkliche Listen darstellen.
Das muss natürlich nicht sein. Kein Mensch kann erzwingen,
dass hier hinten in diesem gepunkteten Paar eine leere Liste drinsteht.
Dort könnte genauso gut Irgendein beliebiges Atom oder eine Zahl oder was auch immer drinstehen.
Dann ist das Ganze keine richtige Liste mehr. Genauso wie eine, ich,
in einem Typtest für die, für die PANO-Zahlen erst dann sagen konnte,
es ist eine PANO-Zahl, wenn ich das innere Nullelement gesehen hatte.
Kann ich hier sagen, es ist eine wirkliche Liste,
wenn ich dieses letzte, den Listenabschluss gefunden habe. Aber ich kann natürlich problemlos durch Unifikation,
das können Sie sich mal dann überlegen,
wenn wir die Unifikation behandelt haben, so etwas aufbauen,
dass ich sage, Ja,
dann habe ich hier eine iterative Aufzählung mit zwei Elementen,
aber hier ist eine eingebettete Liste,
die enthält hier keinen richtigen Listen Ende Marker mehr.
Das ist ein gepunktetes Paar, was vorn dem Verweis
auf B drin hat und hinten den Verweis auf C.
Das kann ich machen, das kann ich als Datenstruktur verwenden,
das ist überhaupt kein Problem. Denn intern ist das nichts,
also der innere Teil hier, nichts anderes als das.
Und das ist ein perfekt wohlgeformter Term, da gibt es
keine Schwierigkeit. Das, was hier rauskommt, ist,
das Ding ist keine richtige Liste mehr.
Also wenn ich den Typtest auch für eine Liste drauf anwende,
wird er diese Liste zerlegen und wird hier hinten feststellen,
ups,
da ist kein Listenende Marker da
Also nicht. Von außen sieht das Ding aber wie eine Liste aus.
Denn auf der obersten Ebene habe ich eingepunktet, das ist Paar vorliegen.
Diesen Unterschied habe ich in allen Pointersprachen, dass ich immer überprüfen muss,
ob das Ding strukturäquivalent ist oder ob es pointeräquivalent ist.
Also in diesem Falle würde ich feststellen, ja,
wenn ich nur auf den Pointer gucke, das ist eine Liste.
Aber es ist keine wirkliche Liste,
wenn ich sie tatsächlich zerlege bis zum letzten Element.
Auf diesen Unterschied wollte ich an der Stelle erstmal nur hinweisen.
So, jetzt gucken wir uns an,
was heißt diese Mantik und zwar nur für den Normalfall,
wir haben
richtige Listen vorliegen
Die also wohlgeformt sind in dem Sinn, dass sie der Definition von Liste,
die ich vorhin angeführt habe, tatsächlich entsprechen.
Also das, wie gesagt, sieht aus wie eine Liste,
ist aber in Wirklichkeit nur ein gepunktetes Paar. So,
ich kann jetzt, weil ich zwei Sichten auf die Listen habe,
kann ich die Unifikation auch in zwei verschiedenen Arten und Weisen definieren.
Einerseits kann ich iterative Sicht zugrunde legen,
wie ist eine Liste iterativ aufgebaut und dann heißt Unifikationsvorschrift einfach nur,
die beiden Listen unifizieren, wenn die Elemente paarweise unifizieren.
Und daraus folgt automatisch, weil ich paarweise Unifikation fordere,
Dass die Listen gleich lang sein müssen.
Denn sonst kann ich eine paarweise Zuordnung nicht herstellen.
Und das Ganze scheitert, wenn irgend an einem dieser Paare die Unifikation misslingt.
Und wer Lust hat, kann sich das ruhig mal veranschaulichen.
Dies ist keine neue Definition von Unifikation,
sondern es ist die Unifikation, angewendet auf solche Strukturen.
Und dann folgt das daraus. Das ist eine Konsequenz aus
der internen Repräsentation und der Definition für Unifikation,
die wir ohnehin schon mal eingeführt haben.
Also wenn ich jetzt so etwas fordere,
die Liste ABC soll mit dieser Liste ABC unifizieren,
dann wird eben paarweise verglichen, das erste Element unifiziert mit dem ersten Element hier.
Das zweite Element hier unifiziert mit dem zweiten Element hier.
Das dritte Element hier unifiziert mit dem dritten Element hier und
damit unifizieren alle Elemente paarweise und ich kriege die Ausgabe,
ja, das ist wahr. Wenn ich an diesen Inlisten,
natürlich kann ich wieder Variablen einbauen, wie in jeder Struktur,
wie in jedem Thermen Variable stehen können, so auch hier,
dann wird eben bei der Unifikation die übliche Vorschrift zugrunde gelegt,
Variable trifft auf Konstante und es wird eine Wertebindung hergestellt
Also, in diesem Fall auf der ersten Position wird Y an A gebunden.
Auf der zweiten Position kriegt X den Wert B und auf
der dritten Position sind sowieso nur Konstanten da.
Da passiert nichts Spannendes mehr, weil ich kriege als Ergebnis heraus,
es wird die Variablenbindung X gleich B,
Y gleich A hergestellt und weitere Ergebnisse gibt es nicht.
Denn Unifikation ist immer eindeutig. Unifikation ist so definiert,
dass sich immer die allgemeinste Struktur kriege, die die Bedingungen erfüllt.
Wenn ich Variable drin habe, heißt das natürlich auch,
dass die üblichen Skopusbedingungen für Variablen erfüllt sein müssen und die einzige Skopusbedingungen,
die wir haben,
ist der Gültigkeitsbereich der Variablen ist die Klausel und die Klausel
hört hier hinten mit diesem Punkt
Auf, wenn also zweimal die gleiche Variable in der Klausel auftaucht,
heißt das auch, dass Chorreferenz zwischen diesen beiden Variablen-Vorkommen bestehen muss.
Und dann wird eben gefordert, dass der gleiche Wert vorliegt.
Also hier wird x gleich A gebunden auf der ersten Argumentstelle.
Auf der zweiten hat jetzt x den Wert A
und muss mit B-Unifiziert werden und das scheitert.
Deshalb kriege ich in diesem Falle also ein negatives Ergebnis.
Und den dritten Fall hatte ich schon diskutiert, ah ja,
sorry, der vierte Fall, den hatte ich schon diskutiert,
dass es eben einfach die paarweise Unifikation ist nicht möglich,
weil die Listen schon unterschiedlich lang sind.
Dann kriege ich eben auch keine Unifikation hin
Ich kann natürlich auch wieder die Rekursive und die rekursive Sicht
werden wir im Weiteren im Wesentlichen verwenden,
weil wir weder rekursive Prädikate definieren müssen,
wenn wir rekursive Datenstrukturen bearbeiten wollen.
Und deshalb wird das die dominierende Sicht auf Listen sein und
mit der müssen wir uns vertraut machen.
Das heißt, zwei Listen unifizieren, wenn die Listenköpfe miteinander
unifizieren und rekursiv die Restlisten ebenfalls miteinander unifizieren.
Und auch da kann man sich zeigen,
dass das keine neue Definition für Unifikation ist,
sondern auch nur die Konsequenz der rekursiven Abarbeitung der internen Datenstruktur
So, also wenn ich jetzt so etwas habe,
wie diese lineare Liste, A, B, C,
D hier und ich habe dieses Listenpattern,
dann muss ich eben den Listenkopf hier mit dem Listenkopf hier unifizieren.
Hier ist der Listenkopf, das erste Element der Aufzählung.
Hier ist der Listenkopf das Ding, was vor dem senkrechten
Strich steht. Und dabei wird X an A gebunden.
Und da hinter dem Strich, das Y steht,
muss die Restliste auf das unifiziert werden,
was nach Abtrennen des Listenkopfes übrig bleibt.
Und das ist eben die Liste B, CD
Das kann ich jetzt natürlich wieder modifizieren.
Da hier vorne nicht nur ein Element zulässig ist,
sondern gegebenenfalls auch zwei Elemente zulässig sind, kann ich jetzt sagen,
gib mir das erste Element und das zweite Element und vergiss die Restliste.
Hier hinten steht eine anonyme Bayable drin, deren Wert interessiert mich nicht.
Und dann wird eben das X an A gebunden,
das Y an B und die Restliste,
die noch CD enthält, die verschwindet im Orgus.
So, und das kann ich jetzt nach Belieben miteinander kombinieren.
Dadurch wird das mehr oder weniger komplex, was man hat.
Also beispielsweise dieses letzte Pattern, was wir hier hart hatten,
mit den zwei Listenköpfen kann ich immer dann verwenden, wenn ich vergleiche,
wenn ich das erste mit dem zweiten Element vergleichen will.
Also beispielsweise beim Sortieren einer Liste.
Es ist eine Liste in der richtigen Reihenfolge sortiert,
dann muss ich immer für benachbarte Elemente überprüfen,
ob sie in der Ordnungsreihenfolge stehen.
Und da brauche ich natürlich Zugriff zu den ersten beiden Elementen jeder Teilliste.
Und dann muss ich durch die Liste rekursiv durchgehen,
um das für alle Elemente nachweisen zu können.
Aber ich brauche erst mal den Zugriff zu beiden Elementen.
Dem kann ich also durch ein solches Listenpattern erreichen.
Jetzt gucken wir uns die Verwendung an. Wozu benutzen wir jetzt
die Listenunifikation? Und das ist wie bei jeder Unifikation.
Wir können sie verwenden A zum Testen,
ist ein Listenelement
Erfüllt es eine bestimmte Bedingung. Wir können es B verwenden,
um eine Liste zu zerlegen in ihre Elemente oder Elemente aus Listen herauszuholen.
Also herausholen, nicht in dem Sinne,
dass sie hinterher nicht mehr drin sind,
sondern in Zugriff zu einzelnen Elementen einer Liste ermöglichen.
Und wir können es benutzen, um Listen aufzubauen.
Tests habe ich hier nicht aufgeführt. Hier ist nur erstmal
der Element Zugriff durch Unifikation. Also ich habe eine Liste
gegeben und durch Unifikation mit solch einem Listenpattern hole ich mir
ein ganz bestimmtes Element aus dieser Liste raus.
In diesem Falle also das erste Element der Liste.
Das kriege ich als Vayablenbindung für
Und wenn ich dieses Pattern hier verwende,
dann heißt das, das erste Element interessiert mich nicht,
das zweite Element interessiert mich nicht, das dritte will ich haben.
Und wenn danach noch was kommt, interessiert mich das auch nicht.
Und dann wird eben dieses Pattern auf diese Liste unifiziert und
ich kriege das dritte Element heraus. Oder mit anderen Worten,
wir nehmen unsere Definition, unsere rekursive Definition,
wir unifizieren die Listenköpfe, anonyme Vagable hier, susi hier,
das gelingt, dann bleibt hier eine Restliste,
Hans-Nina Paul übrig und hier eine Restliste, anonyme Vagable Komma X,
Separator, anonyme Variable.
Da muss ich wieder erst die Kopfelemente unifizieren,
also Hans wird mit der anonymen Variablen unifiziert, das gelingt.
Ich muss wieder die Restlisten bilden, dann wird Nina
mit dem X unifiziert und es bleibt eine Restliste übrig,
die noch Paul enthält.
Es bleibt nicht Paul übrig, vor Sicht,
sondern es bleibt eine Liste übrig,
die noch Paul als einziges Element enthält und diese Restliste interessiert
mich aber auch wieder nicht
Ja, ich kann natürlich auch Listen zusammenbauen.
Das ist wieder der konstruktive Aspekt der Unifikation. Ja,
wir haben immer diese drei Aspekte drin, testen,
zerlegen, zusammensetzen. Und hier ist das Zusammensetzen mal erläutert.
Ich habe also eine Liste gegeben und ich habe eine,
möchte eine Ergebnisliste haben, die so sich bilden soll,
indem ich eine gegebene Liste L mit einem neuen Element nach links anreichere.
Ja, also diese Unifikation,
die jetzt hier dadurch gefordert wird, die Ergebnisliste,
soll dieses Listenpattern sein und L hat bereits diesen Wert,
sodass jetzt die Ergebnisliste die alte Liste mit den vier Namen
drin ist und Karl als erstes Element davor gesetzt ist.
Also hier der Aufruf mit Instanzierten,
Variablen und dann wird durch die Unifikation diese neue Liste gebildet.
Umgekehrt können wir es natürlich machen,
wenn wir diese Liste gegeben hätten und L ist unnützanziiert,
dann würden wir eben wieder eine Restliste daraus extrahieren.
Das sind immer die beiden Sichten,
rufe ich das instanziert auf oder rufe ich es uninstanziert auf?
Und das kann man wieder beliebig komplex machen.
Dieses Pattern sagt mir jetzt, zerlege diese Liste in ein
erstes Element, in ein zweites Element und eine Restliste
Und verwende die so erzeugten Zwischenergebnisse dann, um eine Ergebnisliste aufzubauen,
die aus dem ersten Element, dem zweiten Element,
einem eingeschobenen Element besteht und der alten Restliste.
Und dann kriege ich eben als Ergebnisliste diese ursprüngliche Liste,
nur ist der Karl diesmal nicht an den Anfang geschoben,
sondern nach dem zweiten Element.
Genau wie dieses Pattern, das ihr vorseht.
Also Karl soll nach dem zweiten Element eingefügt werden.
Bei der Gelegenheit hätte ich auch die Reihenfolge von Elementen vertauschen
können, wenn das die Aufgabe erfordert.
Also verschiedene Verwendungsmuster, um zu sagen,
wie soll eine Liste
zusammengebaut werden
Und das Schöne ist jetzt eben wieder,
diese beiden Sichten, Analyse oder Zerlegen einer Liste und Zusammensetzen
einer Liste sind in ein und derselben syntaktischen Notationen vereinigt.
Und im Programm dann auch ein und derselbe Ausdruck.
Und wegen der Richtungsunabhängigkeit sollte man hoffen zumindest.
Und davon müssen wir uns überzeugen, dass man das dann
in beliebiger Richtung anwenden kann.
Ja, das Ganze kann man jetzt beliebig komplex machen.
Das können wir durchexerzieren. Also mit eingebetteten Listen jetzt.
Bisher haben wir nur Linearelisten betrachtet. Jetzt kommen mal eingebettete
Listen dazu. Wir haben hier eine Liste,
die hat einen Kopf
Vor diesem Kopf separator steht genau ein Element und das ist wieder eine Liste.
Und das müssen wir hier mit dem Kopf dieser Liste unifizieren,
also kriegst X den Wert Liste AB.
Dann müssen wir die Restliste, die übrig bleibt,
nach dem Abtrennen des Kopfes, mit diesem X-Modifizieren,
was bereits jetzt einen Wert hat, nämlich diese Liste.
Und die Restliste hier nach Abtrennen des Kopfes ist aber genau die Liste,
die AB enthält, also in diesem Falle ist das Ganze erfolgreich.
Weil hier die Chorreferenzforderung eben erfüllt ist,
dass die Restliste, die sich hier ergibt,
die Liste AB ist und hier das erste Element dieser Liste
eben auch AB ist,
Glück gehabt,
kann man nur sagen
Gut, das zweite ist ein bisschen komplizierter. Da muss man jetzt wirklich,
und das können wir mal durchexerzieren, ganz,
ganz stur durchrechnen, wenn man sich das überzeugen will,
ob es unifiziert oder nicht.
Also, was muss ich hier machen? Ich habe,
wir können ja mal so anfangen, ich habe links eine 3-Elementige Liste vorliegen.
Ja, die hat hier ein erstes Element,
sie hat hier ein zweites Element,
das ist eine Liste und das dritte Element ist ebenfalls eine Liste.
Und hier rechts haben wir hier ein Element,
ein zweites Element und ein drittes Element.
Also die Anzahl der Elemente schon mal gleich,
das ist ein ganz gutes Zeichen.
Also müssen wir jetzt paarweise
miteinander unifizieren
Wie gesagt, welche Sicht man zugrunde legt, ist egal.
Es trifft also die Vajable O, die war auf der linken Seite,
haben auf das erste Element auf der rechten Seite,
und das ist eine Liste, die B,
als erstes Element und die vajable Kuh als zweites Element hat.
Damit ist die erste Listenposition abgearbeitet, auf der zweiten Listenposition,
tritt jetzt die Liste P, Komma Q,
also vajable P und vajable Kuh auf die Liste B,
also Atom B, Komma, gefolgt von einer Liste,
die Atom C und Vajable P enthält.
Und damit haben wir eine neue Unifikationsforderung aufgebaut. Wir müssen jetzt nachweisen,
dass diese Unifikation tatsächlich gelingt und kriegen damit eine vajablen Bindung
für P. Erstes Element hier und erstes Element hier,
P ist gleich B. Steht das da unten irgendwo?
Ja, wir sind offensichtlich noch auf dem richtigen Pfad.
Wir müssen die zweite Argumentstelle unifizieren und kriegen dann,
dass die Bindung Q ist gleich Liste,
Atom C gefolgt von Valiable P. Oh,
den Wert für Valiable P kennen wir schon,
den können wir also hier
rein substituieren
Und das ist also die Liste C, Komma B, mal vergleichen.
Ja, da steht da auch noch, das sieht gut aus.
Damit haben wir alles unifiziert, was auf der zweiten Listenposition unifizierbar ist.
Kommt die dritte Listenposition. Das ist eine Liste,
die enthält A, Komma O, also Valable O.
Und auf der rechten Seite Liste, die Atom A enthält,
gefolgt von Liste, die Atom B enthält,
gefolgt von Liste, die die Valable R enthält,
mit dem gefolgt vom Atom B.
Und dann müssten drei Klammern zugehen.
Und damit haben wir wieder eine neue Unifikationsgleichung, die wir abarbeiten müssen, aufgestellt.
Also hier erstes elementieren muss mit erstem Element hier unifizieren.
Das klappt. Es wird keine vaiablen Bindung hergestellt.
Das O muss mit dieser Liste unifizieren und für O haben wir
hier oben bereits einen Wert da. Das heißt also,
der Wert, den wir hier haben,
er muss unifizieren mit der Liste, die wir hier unten haben,
also B, gefolgt von Liste, vaiable R, Komma
B und dann zwei Klammern zu. Und wir müssen jetzt
diese wieder unifizieren? Haben wir hier unten schon alles erledigt?
Ja, dann sind wir hier unten durch. Dann müssen wir
die beiden miteinander unifizieren. Das gelingt auf der ersten Listenposition,
auf der zweiten wird Kuh an das Ding hier gebunden.
Für Kuh hatten wir bereits einen Wert, der steht hier.
Der muss also unifiziert werden mit dieser Liste,
R-Komma-Atom B. Und damit müssen wir diese Unifikation noch durchrechnen.
Damit kriegt er den Wert C. Ja, weil ich jetzt
hier erste Listenposition hier mit erster Listenposition hier unifizieren muss.
Und zweite Listenposition mit zweiter natürlich. Und dadurch wird
die Valiable R mit dem Wert B versorgt
So, sind wir damit durch, dann wissen wir jetzt,
was O ist, denn wir kennen jetzt auch den Wert von R, also die Liste, die B, gefolgt von C, Komma
B als Liste enthält. Ja, das ist der Wert für O,
das ist der Wert für P, das ist der Wert für Kuh,
der steht hier und hier unten steht der Wert für R. Also,
das ist ein bisschen mühsam, die gute Nachricht ist,
dass man natürlich sehr selten in die Gelegenheit kommen wird,
solche Strukturen per Hand miteinander zu unifizieren, die Unifikationsaufgaben,
die wir haben, sind üblicherweise wesentlich einfacher,
aber man muss eben mit diesen beiden Sichten iterative Sicht, rekrusive Sicht auch dann noch spielerisch umgehen können, wenn die Dinge ineinander eingebettet sind.
Solche Fälle werden wir haben und damit muss man umgehen können.
Insofern muss man mal ein bisschen sich auch auf der Ebene
der formalen Semantik dieser Unifikation ein bisschen spielerisch damit beschäftigen.
So, jetzt geht es also hin, dass wir tatsächlich
ein Programm schreiben wollen und wir fangen wieder mit ganz einfach an.
Wir wollen ein Programm schreiben, was uns das erste Element einer Liste liefert.
Das ist ja eine typische Accessor, Methode in der objektorientierten Programmierung.
Gib mir das erste Element einer Liste. Möglich,
dann natürlich gibt man auch andere Elemente einer Liste,
aber wir fangen mit dem einfachsten an. Also,
was wir haben wollen, ist, wir haben eine Liste gegeben,
wir wollen jetzt ein Element wissen und,
das hatte ich ja als Ziel vorweggestellt,
wir sind jetzt im Kernbereich der Logikprogrammierung,
wir haben es mit Strukturen zu tun, keine Arrithmetik,
nichts, es müsste richtungsunabhängig sein.
Also gewünscht ist natürlich, sowohl das als auch das
soll uns uninstanziert aufrufbar sein
Und wir müssen wieder definieren,
was ist das gewünschte Verhalten, positiv und negativ,
also das erste Element dieser einer Liste ist das A,
das erste Element dieser Zweierliste ist ebenfalls ein A,
das erste Element dieser Dreierliste ist ebenfalls ein A,
das erste Element einer Lehrenliste soll nicht existieren,
denn da ist kein erstes Element,
das erste Element einer Liste, die mit A anfängt,
kann nicht B sein,
ist also auch ein negatives Ergebnis und wir machen wieder genau das Gleiche,
was wir bisher auch gemacht haben,
wir gucken uns die positiven Beispiele an und versuchen aus
den Beispielen induktiv zu schließen,
was ist das Gemeinsame an diesen Beispielen
Und wir müssen dieses suchen,
nach dem gemeinsamen jetzt durch die Brille unserer Unifikationsoperation machen.
Also entweder die iterative oder die rekursive Sicht, die wir haben.
Und ich kann es gleich vorwegnehmen, üblicherweise brauchen wir die rekursive Sicht.
Weil nämlich, wenn wir die nicht anwenden,
haben wir es immer mit endlichen Listen zu tun,
deren Länge festgelegt ist. Iterative Sicht heißt immer,
ich weiß, wie viele Elemente diese Liste enthält.
Wenn wir mit beliebig langen Listen umgehen und nur dann macht
das Ganze Sinn, brauchen wir die rekursive Sicht.
Also wir müssen immer gucken,
können wir ein solches Listenpattern hier auf diese Daten anwenden und
dann die Relation beschreiben,
die zwischen der ersten oder der zweiten Argumentstelle besteht
Und hier ist es trivial. Wir gucken drauf und wir sehen sofort,
ja, das gesuchte Element hier steht immer hier vorn.
Und das ist eben genau das Element, was vor dem Listenkopfseparator steht.
Das erste Element und danach kann etwas Beliebiges kommen.
Das interessiert uns nicht. Und das schreiben wir hin.
Also das gesuchte erste Element ist das erste Element dieser Liste.
Alles, was danach kommt, interessiert uns nicht.
Auch hier haben wir wieder ein Beispiel für ein Programm,
was nur aus einem Fakt besteht.
Und trotzdem in der Lage ist, mit unendlich vielen Daten
umgehen zu können. Denn es gibt unendlich viele Listen,
auf die man
dieses Prädikat anwenden kann
Also ein Nicht-Grundterm, der noch Vagabel enthält, der dadurch,
dass er Vagabel enthält, dank der Unifikation eine prozedurale Semantik bekommt.
Das ist also nicht einfach nur ein passives Datenelement,
sondern ein aktives Element, was ein Berechnungsverhalten hervorrufen kann.
So, und dann gucken wir uns eben an, wie das funktioniert.
Wir können es wieder uninstanziert im ersten Argument aufrufen.
Das ist die kanonische Verwendungsweise für die Accessor-Methode beispielsweise.
Also was ist das erste Element dieser Liste und wir unifizieren
jetzt nur dieses Pattern, was in unserer Definition steht,
auf die zwei,
ups,
sorry,
auf die zweite Argumentstelle
Ja, und wir kriegen damit das gesuchte Element heraus.
Also das A als Einzelbindung für das Ex.
Wir können es genauso wieder aufrufen, uninstanziert, im Zweiten,
bilde mir alle Listen, die A als erstes Element enthalten.
Und wir kriegen wieder eine unterspezifizierte Antwort, die sagt,
ja, es sind alle Listen,
wo hier ein A steht und wo eine beliebige Restliste auftreten kann.
Ja, wo wir wieder das,
was wir schon mehrfach gesehen haben, auch bei der PANO-Arathmetik,
wo wir eben mit einem Rutsch unendlich viele Ergebnisse beschreiben können.
Denn hier hinten kann ich sowohl unendlich,
also beliebig lange Listen und damit unendlich viele unterschiedliche Listen einfügen,
als auch die Elemente dieser Listen sind vollkommen beliebig.
Darüber sagt das Programm nichts
Wir haben hier einen interessanten weiteren Fall zu betrachten.
Also das war uninstanziert im ersten Argument, uninstanziert im zweiten Argument.
Wir können das Ganze natürlich vollständig uninstanziert aufrufen. Hier aber
der Fall, wenn wir noch eine Co-Referenz drin haben.
Wir fordern jetzt, dass das erste Element einer Liste
die Liste selbst ist. Das ist die Chorreferenz,
die wir hier fordern. Also gib mir eine Liste,
die sich selbst als erstes Element enthält.
An dieser Stelle sollten sie an Escher erinnert sein.
Also Strukturen,
die sich selbst
wieder enthalten
Und das ist wieder eine Neuerung von SWI-Prolog. Neuerdings kriegt man
dann das als Ergebnis und diese zwei Sterne sollen bedeuten,
hups, jetzt ist eine zyklische Liste aufgebaut worden.
Eine Struktur, die unendlich, die kein Ende hat.
Aus dieser Notation sieht man überhaupt nicht, was dort eigentlich tatsächlich passiert.
Wir müssen uns, das haben wir schon mal gehabt,
den Fall genauer angucken, was dort passiert.
Und da müssen wir ein bisschen Aufwand treiben.
Ich habe jetzt hier, oder wir fangen mal so an,
wir gucken uns erstmal anhand der internen Repräsentation an,
was sie eigentlich gerade passiert
Wir haben ein Programm, was sagt, du hast hier eine Liste,
bestehend aus zwei Elementen, einem Kopf und einer Restliste.
Und jetzt ist gefordert, dass diese Liste sich selbst enthält.
Intern heißt das, wir haben eine Kunstzelle, auf die,
das ist die Liste, über die wir reden.
Und wir wollen jetzt erreichen,
dass hier der Point da nicht auf irgendein Element,
so bisher hatten wir hier sowas wie A stehen,
das wollen wir nicht.
Wir wollen diesen Point da umbiegen,
dass er wieder auf die Liste selbst zeigt
Und damit haben wir den Zyklus aufgebaut,
dass eine Liste sich immer wieder selbst enthält.
Also auf der ersten Stelle dieser Struktur passiert jetzt ein
unendlicher Regress in die Tiefe. Und um den nachzuweisen,
habe ich hier mal ein bisschen Patricks verwendet.
Ich habe also Listenpattern aufgeschrieben, die genau auf der ersten
Argumentstelle dieser Liste einen Abstieg in die Tiefe machen.
Also dieses X gibt mir dann
das erste Element der Liste
Hier, also, das A gibt mir das erste Element der Liste.
Also, ich unifiziere diese Liste, die hier gebaut worden ist,
ja, denn das Ganze ist ja unterspezifiziert.
Also, ist da was konstruiert worden? Nämlich genau
diese Liste, die dort rechts steht, diese zyklische.
Diese Liste nehme ich und lasse mir ihr erstes Element geben.
Und gucke mir das an. Und als nächstes lasse ich
mir vom ersten Element das erste Element geben.
Denn es ist ja wieder eine Liste hier drin.
Und als drittes lasse ich mir vom ersten Element das
erste Element und von dem wieder das erste Element geben.
Ja, und sie merken schon,
warum ich die Cartoon an den Anfang gestellt habe,
diese Selbstbezüglichkeit spielt jetzt immer eine größere Rolle in solchen Fällen.
Und dann sehen wir, dass wann wie tief immer wir
hier hineingehen, das ist natürlich noch kein Beweis.
Wir könnten das jetzt für vier, für fünf,
für sechs Rekursionsebene machen und wir würden sehen,
dass die Bindung, die wir bekommen, ist immer wieder dieselbe.
Wir sehen immer wieder hier vorn irgendetwas und hier hinten den
Verweis auf die gleiche Restliste. Gut, das ist kein Wunder,
da an dem hinteren Teil der Liste haben wir ja auch überhaupt nichts gemacht.
Das ist ja die anonyme Variable, die wir in der Definition verwendet haben.
Die kriegen wir hier wieder. Aber hier vorn,
das sagt uns immer wieder, wie tief auch immer wir reingehen,
Das Ding ist weiter rekursiv eingebettet.
Und zwar so, dass ich es nicht ausdrucken könnte.
Denn der Ausdruck dieser Liste würde ja jetzt lauten,
ja, ich bin eine Liste, die eine Liste enthält,
die eine Liste enthält, die eine Liste enthält, die eine Liste enthält.
Und jede Printroutine würde das bis Ad-Infinitum machen.
Früher hat das Prolog-System dann hier irgendwann mal Punkte reingemacht und
dann hier hinten die Listen einfach wieder zugemacht.
Fand ich plausibler. Ich finde diese Notation einfach unglücklich.
Aber falls sie die mal bekommen, sollten sie wissen,
was sie bedeutet. Die ist nicht selbsterklärend. Die andere
Notation war selbsterklärend. Da hat man gesehen, hups,
hier ist die Liste jetzt beliebig oft ineinander eingebettet.
Das ist nicht das, was ich vielleicht haben wollte
Gut, wir können natürlich auch, das war auch eine partiell-unterspitze,
wie gesagt, sorry, die hier,
eine partiell-unterspezifizierte Anfrage, wo wir zwar beides variablen versehen haben,
aber noch die zusätzliche Co-Referenzforderung mit drin haben.
Und wir können das Ganze,
und jetzt sind wir wieder auf dem normalen Bereich der sinnvollen Verwendung des Ganzen,
wir können das Ganze vollständig unterspezifiziert aufrufen und wir kriegen dann ein vollständig unterspezifiziertes Ergebnis,
was uns sah, ja,
das erste Element der Liste kann natürlich ein beliebiges sein.
Hauptsache, es ist in der Liste als erstes Element enthalten.
Alles andere interessiert mich nicht
Gut, dafür sehe ich wieder keine sinnvolle Verwendung für die anderen Verwendungsweisen.
Da werden wir uns tatsächlich nicht bei dem First,
sondern bei den anderen Prädikaten dann ein Beispiel angucken, wo das Sinn macht.
Auch hier, das habe ich vielleicht etwas voreilig gesagt,
auch für diese rekrusive Einbettung einer Liste.
Was ich ja hier mache, ist ja im Prinzip unendliche Listen erzeugen.
Auch dafür gibt es involle Anwendungen. Das ist nicht so,
dass man da nicht manchmal gewünschte Effekte erzeugen kann. So,
ein sehr häufig benutztes Prädikat,
was wir auch ganz am Anfang der Vorlesung schon mal gesehen hatten,
ist das Prädikat, was testet,
ob ein Element in einer Liste enthalten ist, ja oder nein.
Und das gewünschte Verhalten, also ich habe hier wieder
ein Element, ich habe eine Liste. Ach so,
im Übrigen bei dem vorhergehenden Prädikat mit dem ersten Element,
war ja die Frage, ob das Ding wirklich in allen Instanzierungsvarianten verwendet werden kann, ob wir das erreicht haben.
Und wenn wir uns die Ergebnisse hier angucken, ja,
müssen wir sagen, ja, wir können es.
Jetzt also das Testen auf Enthaltensein in einer Liste,
wir haben wieder positive Beispiele, wir haben negative Beispiele,
Zu den positiven Listen, Beispiel zählt, eine Liste,
die nur A enthält, enthält natürlich A, eine Liste,
die A, B enthält, enthält auch A.
Eine Liste, die A, B, C enthält,
enthält auch B und eine Liste, die A,
B, C enthält, enthält auch C. Sie sehen,
ich habe in dem Beispiel hier ausdrücklich die verschiedenen Grenzfälle wieder aufgenommen.
Sie erinnern sich an unsere Testvorgaben. Wir müssen immer gucken,
was passiert in den Grenzfällen. Ich habe also hier bewusst gesagt,
eine Liste, die nur das Element enthält,
eine Liste, die das Element am Anfang enthält,
aber noch weitere, eine Liste,
die das Element irgendwo in der Mitte enthält und rechts und links davon sind andere und eine Liste,
die das Element an der letzten Stelle enthält.
Damit sind die wichtigen Grenzfälle abgedeckt und wir haben auf der rechten Seite wieder negative Fälle,
also in der leeren Liste ist nichts enthalten.
Deshalb sollte auch kein A drin enthalten sein und in der
Liste die ABC enthält, ist auch kein D enthalten.
Deshalb sollte das scheitern. Und wir schauen jetzt wieder durch
die Brille unserer Unifikation und zwar dieses Recosiven Listen-Patterns auf diese Daten
und müssen sagen, ja, was ist denen eigentlich gemeinsam?
Und dann sehen wir sofort,
dass wir die ersten beiden Testbeispiele mit dem Pad dann beschreiben können,
was wir gerade für das erste Listen-Element benutzt haben
Nämlich, wenn ein Ding das erste Listen-Element ist,
dann ist es in der Liste natürlich enthalten.
Das sind die ersten beiden Testbeispiele, die dort aufgeschrieben sind.
Und wenn wir die generalisieren zu einem Fakt,
dann kommen wir genau auf das, was eben die Definition für First war.
Und die steht als erste Klausel hier unten in der Prädikatsdefinition
drin. Also wenn irgendetwas Kopf einer Liste ist,
dann ist es auch in der Liste enthalten.
Und da müssen wir uns also noch um die restlichen Fälle
hier kümmern und die restlichen Fälle sagen, jetzt eben ja,
wenn das Ding nicht der Kopf ist,
dann muss es irgendwo weiter hinten in der Liste enthalten sein
Und das schreiben wir auf. Also das gesuchte Element ist nicht der Kopf,
sondern muss weiter hinten irgendwo in der Liste sein.
Also müssen wir nachweisen, dass das gesuchte Element in der Restliste,
die nach Abtrennen des Kopfes übrig bleibt, enthalten ist.
Und damit haben wir die beiden Fälle, entweder das Element das erste,
das erste in der Liste oder es ist irgendwo in der Restliste versteckt.
Und das ist eine typische rekursive Definition über einer linearen Liste.
Und das geht natürlich auch,
dann das funktioniert auch für rekursiv eingebettete Listen,
aber die Rekursion passiert natürlich nur auf einer Ebene hier.
Ich gehe die Liste der Reihe nach durch,
trenne immer das erste, das Element ab,
wenn es denn nicht das gesuchte Element ist und schaue danach
nach,
was in der Restliste enthalten ist
So, und das können wir jetzt wieder in verschiedenen Varianten testen,
die erste Variante ist,
ich habe eine Liste gegeben und ich möchte deren Elemente wissen,
dass es jetzt die generative Sicht, ja,
und man kriegt dann also als Alternativen aufgezählt ein Element nach dem anderen.
Das kann man sich also sehr, sehr leicht an der Prädikatsdefinition nachvollziehen.
Die erste Klausel liefert mir das erste Element als Ergebnis,
wenn ich nach Alternativen frage,
wird dieses Listenpattern auf die Liste unifiziert,
die Restliste extrahiert und das Ganze rekursiv aufgerufen und
mit Hilfe der ersten Klausel wird das erste Element der Restliste
als nächstes Ergebnis ausgegeben
Wenn ich wieder nach Alternativen frage, wird das Gleiche
noch mal gemacht. Es wird wieder die Restliste gebildet,
also ein Element beseitigt, die Restliste gebildet und über den Rekordsieben
Aufruf wird mir das erste Element geliefert. Solange,
ja, wie lange eigentlich? Ja. Genau,
also wenn die Restliste irgendwann mal der Listenende mag,
also die leere Liste ist, dann unifiziert keine der beiden Klauselköpfe mehr damit.
Denn hier wird eine Liste gefordert,
die mindestens noch ein Element enthält und hier wird eine Liste gefordert,
die auch mindestens noch ein Element enthält.
Also für die leere Liste scheitert der Aufruf immer
Und das ist unser rekursiver Abschluss, der dann dafür sorgt,
ja weitere Elemente gibt es nicht.
Das können Sie sich schon mal merken,
das ist ein typisches Pattern, womit ich einfach mal testen kann,
ist das eine nicht leere Liste.
Enthält sie noch irgendwelche Elemente, ja oder nein?
Und das ist Teil der Definition hier, dass ich genau diesen Test durchführen muss.
Ja, die zweite Verwendungsweise, in welchen Listen ist Aden enthalten?
Und dann werden wir alternativ die Listen konstruiert,
in denen A enthalten ist. Und wir sehen,
wir kriegen unendlich viele Ergebnisse,
die jeweils wieder für unendlich viele Listen stehen
Ja, also die erste Antwort sagt, die HLA ist
in allen Listen enthalten, wo A an erster Stelle steht.
Die zweite Antwort ist A, ist in allen Listen enthalten,
wo A an zweiter Stelle steht. Und die dritte sagt,
ja, alle Listen, wo A an dritter Stelle steht.
Und wenn ich Geduld habe, kann ich das Ad Infinitum fortsetzen.
Ich kann es auch wieder vollständig unterspezifiziert aufrufen und dann wird
dieses Muster hier nur nochmal weiter aufgebläht.
Jetzt sage ich nur, mein X ist eben ein X1,
so das X1 steht hier in der ersten Position,
hier in der zweiten,
hier in der dritten und so weiter und so fort
Also die ursprüngliche Frage, auch hier wieder,
wir können es tatsächlich in allen Instanzierungsvarianten aufrufen,
wir kriegen nichts, keine Terminierungsprobleme dazu. Das terminiert immer,
auch wenn es denn möglicherweise unendlich viele Entscheidungspunkte erzeugt, dann ist es unsere Angelegenheit,
rechtzeitig aufzuhören und etwa nicht auf die Idee zu kommen,
ein Find All über diesen Aufruf zu machen.
Dann hat aber das Find All das Problem, nicht dieses Prädikat. Gut,
und das ist eben das Prädikat Member,
das hat mir ganz am Anfang schon mal gesehen und das ist eines der Prädikate,
wo wir dann zum Schluss auch mal vergleichen werden,
funktionale Programmierung oder was ich an Anwand gestellt hatte,
da war der Vergleich ja schon da, funktionale Programmierung,
auch die imperative Programmierung mit der Spezifikation in der Logikprogrammierung.
Und was wir hier sehen, ist jetzt tatsächlich,
dass wir diese deklarative Sicht langsam pflegen können,
dass wir nicht mehr fragen, was macht dieses Programm eigentlich,
sondern was spezifiziert ist.
Und was so spezifiziert, ist eben einfach, die sagen,
wie es ist, das erste Element einer Liste ist in der Liste enthalten.
Und wenn ein Element in der Restliste enthalten ist,
dann ist es ebenfalls ein Element der Liste.
Das ist die deklarative Spezifikation,
der Relation zwischen einem Element und zwischen einer Liste und
ihren Elementen
Und die verschiedenen prozeduralen Varianten, ich teste, ich suche und so weiter,
das sind dann unterschiedliche Aufruf, Varianten, dieses deklarativen Programms.
Ja, wir gucken uns der Vollständigkeit halber nochmal die Rekursionsschemata an.
Also hier der Aufruf, welche Elemente sind in der Liste ABC enthalten.
Da komme ich kaum noch an gegen das Licht hier.
Wir wenden also als erstes, wir kriegen ein erstes Ergebnis A für A
und hier ist jetzt nur der rekursive Aufruf immer dargestellt.
Danach wird also die Restliste gebildet und es wird nochmal die
Restliste gebildet und dann die leere Liste unifiziert nicht mehr.
Und das heißt also, ich kriege die drei Ergebnisse und
es wird hier einfach eben nur diese Co-Referenzkette aufgebaut,
sodass also hier schon immer klar ist,
das dritte Element ist eben dann das gesucht X2.
Und weil ich weiß,
dass X2 gleich X1 ist und X1 ist gleich X,
das kenne ich also hier unten schon das Ergebnis und das Ganze ist wieder endrikusiv,
was auch die Definitionen ja nahelegt.
Der Ops, das war die falsche Richtung.
Der rekusive Aufruf ist das letzte Teilziel in dieser Klausel hier.
Das Ganze ist also nach unserer Vereinbarung endrikusiv
Das Ganze umgekehrt aufgerufen, baue ich hier jetzt auch wieder
eine Cho-Referenzkette auf, dass ich sage,
X ist im ersten Fall in der Restliste enthalten.
Nur kenne ich die Restliste noch nicht. Im zweiten Fall
wird diese Restliste instanziert, aber ich kenne das X2 noch nicht,
bis es im Rekursionsabschluss mit der Lehrenliste instanziert wird.
Und dann habe ich also im Ergebnis diese verschiedenen Listen mit
den gewünschten Elementen an den unterschiedlichen Stellen.
Tatsächlich ist es aber auch hier wieder so,
das Ergebnis stand im rekursiven Abstieg bereits fest.
Und diese gestrichelte Linie soll nur andeuten,
ich kann natürlich beliebig viele Rekursionsebenen abarbeiten,
wenn ich das möchte
Während das hier natürlich natürlich terminiert,
wenn die Liste rekursiv abgearbeitet ist, dann ist hier Schluss.
Und dann sehen wir auch, hier erfolgt eben die Konsumtion auf dieser Argumentstelle.
Da wird eine komplexe Struktur immer wieder einfacher gemacht.
Das passiert hier nicht. Und das ist genau der Grund,
weshalb ich dann unendlich viele Ergebnisse bekomme.
Ja, aber allesamt korrekt sind. Das ist ja nichts Ungewöhnliches
. Aber wir sehen auch, das Ganze ist endrekusiv.
Ich brauche den Rekursiven Aufstieg gar nicht mehr.
Ich kriege im rekursiven Abstieg bereits alles zusammengerechnet
Jetzt kombinieren wir mal die Listenverarbeitung mit der Arithmetik,
die haben ja jetzt beides in der Hand und wollen ein Prädikat schreiben,
was uns die Länge einer Liste berechnet, wieder die gewünschten Ergebnisse,
die leere Liste soll die Länge null haben,
die einer Liste,
die Länge 1 und so weiter und hier wieder ein paar falsche Ergebnisse und wir müssen wieder durch die Brille unseres Unifikationspatterns auf diese Listen schauen und versuchen,
das gemeinsam herauszuarbeiten und wir kriegen hier nicht mehr hin,
als dass wir als Rekursionsabschluss bekommen,
die Lehreliste hat offensichtlich die Länge null.
Alles andere passt nicht darauf
Und wir sehen,
wir können einen komplexen Fall auf einen einfachen Fall zurückführen,
indem wir sagen, ja,
wenn wir aus dieser einer Liste hier ein Element wegnehmen,
dann führen wir es auf diesen Fall zurück.
Da müssen wir hier hinten aber auch eins runterzählen.
Und wenn wir dieser Liste hier eins wegnehmen,
dann führen wir es auf diesen Fall zurück.
Und den Fall können wir wieder auf den zurückführen. Also damit,
dass das Rekursionsschema auch klar, wir sagen einfach,
die Lehreliste hat die Länge Null und ansonsten müssen wir
diese Liste in Kopf- und Restliste zerlegen.
Der Kopf interessiert uns überhaupt nicht, den wollen wir nicht wissen.
Wir wollen nur wissen, dass er da war.
Und wir ermitteln die Länge der Restliste
Und diese Restliste müssen wir noch mal um eins subtrahieren,
damit wir dann auch die korrigierte Liste für die korrigierte Länge
für die um eins kürzere Liste erhalten.
Ist das ein endrekursives Prädikat? Es ist nicht nach dem rekursiven Aufstieg,
wird noch gerechnet, und zwar dann im rekursiven Aufstieg.
Was habe ich gerade gesagt, im rekursiven Abstieg steht das Ergebnis
noch nicht fest, es muss im rekursiven Aufstieg berechnet werden.
Gut, wir können das jetzt wieder aufrufen mit dem kanonischen Fall,
gib mir die Länge dieser Liste und das scheint zu funktionieren.
Wir können es in der umgekehrten Variante aufrufen und kriegen,
also da hatten die Frage,
welche Liste ist drei Elemente lang und kriegen das richtige Ergebnis,
was aber auch wieder unendlich viele Listen beschreibt.
Denn ich kann hier beliebige Strukturen, Atome,
Zahlen und so was hinein udifizieren.
Und davon gibt es unendlich viele. Wenn ich jetzt nach
einer Alternative frage, dann schmiert das Ding wieder ab.
Und so ein ähnliches Verhalten haben wir schon mal gesehen,
das liegt natürlich ganz einfach daran,
dass ich eben einmal den Abstieg bis zur Null gemacht habe.
Und wenn ich jetzt noch weitere Ergebnisse will,
dann wird eben die Rekursion immer weiter durchgeführt.
Es wird immer wieder MyLenk aufgerufen und es geht weiter
in die Tiefe, in die negativen Zahlen hinein.
Und ein zweites Mal kommt dieser Rekursionsabschluss nicht vorbei und
deshalb terminiert das Ganze nicht mehr.
Wie könnten wir
es verhindern
Hat die mal eine Idee? Okay,
wir machen es endrekusiv, das gucken wir uns gleich an.
Aber hier an der Stelle sehe ich erstmal keine schnelle Reparatur dafür.
Das Problem besteht eben darin, dass dieses Terminierungsproblem dann auftaucht.
Nein, in dem Falle könnten wir
es machen
Wir könnten diesen Fall reparieren, indem wir einfach testen,
ob das Ding noch positiv ist. Dann dort mal weiter
rekursiv abbauen. Aber wenn wir da diesen Test einbauen,
dann machen wir uns natürlich diesen Fall kaputt.
Dann, weil wir von einer nicht instanzierten Zahl wissen,
ob sie größer null ist. Und das würde nicht funktionieren.
Also es ist eine einfache Reparatur ist da nicht in Sicht.
Wir gucken uns lieber mal den, ach nein,
den gucken wir uns nicht mehr an.
Nee, die ist entrekursiv, habe ich nicht dabei.
Aber das wäre tatsächlich die Lösung, die wir
etwas besser hinbekommen könnten
Wir schauen uns stattdessen nochmal die Untraspezifizierte Anfrage an.
Also welche Listen haben welche Längen? Und wir kriegen dann
eben hier die leere Liste, hat die Länge null,
die einer Liste, die Länge 1 und so weiter und so fort.
Also auch das sind offensichtlich plausible Antworten,
aber eben wegen der Arrethmetik, die wir hier haben,
sind wir nicht mehr unbegrenzt richtungsunabhängig.
Das ist kein Wunder. Immer in dem Augenblick,
wo wir in einer Prädikatsdefinition hier eine solche arrethmetische Auswertungsumgebung einführen,
dann geben wir die Richtungsunabhängigkeit auf.
Das ist sofort immer
die unmittelbare Konsequenz davon
So, wir gucken uns stattdessen lieber mal ein Beispiel an,
wo wir jetzt tatsächlich wieder die Richtungsunabhängigkeit gegeben haben.
Und das ist ein typisches Beispiel,
was die Mächtigkeit der Unifikation auf einen Punkt bringt.
Wann immer irgendjemand hingeht und ein Unifikationsformalismus entwickelt,
und es gibt kompliziertere als die Thermonifikation,
die wir uns hier angucken,
dann wird er als allererstes zeigen,
dass das appent funktioniert
Er pennt, heißt, ich möchte zwei Listen mit einer Gesamtliste verketten.
Also ich habe hier eine Liste gegeben und eine zweite Liste
gegeben und ich möchte diese beiden Listen in eine vereinigen,
sodass die Elemente der ersten Liste vorn stehen,
die der zweiten Liste dahinterstehen.
Und zwar in der ursprünglichen Reihenfolge. Das ist erpennt.
Und ich möchte natürlich, dass das Ganze wieder richtungsunabhängig ist,
das heißt, dass es in allen Instanzierungsvarianten funktioniert.
Und wenn wir das zeigen können, dann können wir zeigen,
dass wir wirklich ein logisch saubere Implementierung der Unifikation vorliegen haben.
Also die positiven Fälle, und jetzt wird es ein bisschen kompliziert,
da ist die leere Liste verkettet mit der leeren Liste,
ist wieder die leere Liste,
die leere Liste verkettet mit einer einer Liste,
ist diese
einer Liste
Die leere Liste verkettet mit einer Zweierliste, ist die Zweierliste. Die Liste,
die A enthält mit der Liste, die BEC enthält,
sei die Liste ABC ergeben, die Liste,
die AB enthält mit Dallas und so weiter und so fort.
Wir haben auch ein paar negative Beispiele,
also eine leere Liste mit einer etwas gefüllten Liste,
soll nicht die leere Liste ergeben und es sollen keine Elemente verloren gehen beim Verkettungsprozess.
Also da kann man beliebig viele negative Fälle noch aufführen,
die für Testzwecke möglicherweise interessant sind. So und jetzt wird
es zunehmend schwierig, hier drin ein Muster zu erkennen.
Bisher war das immer relativ einfach, aber jetzt in diesem Wust
hier ein gemeinsames Muster zu erkennen, ist nicht ganz einfach.
Deshalb müssen wir uns die Brille nochmal aufsetzen,
die wir haben und die Brille sagt, jawohl,
und dann fangen wir, nein, später,
wir setzen uns die Brille später auf.
Erstmal gucken wir uns die ersten Fälle an.
Also offensichtlich sehen wir so lange wie hier vorne
die eine leere Liste steht auf der ersten Argumentstelle.
Dann können wir ganz einfach sagen, muss die zweite Argumentstelle
gleich der dritten sein. Das gilt für die Lehreliste,
das gilt für die eine Liste, das gilt für die Zweierliste.
Wir haben gute Chancen, dass das auch für Dreier,
Vierer und Fünferlisten gilt
Das riecht danach, als hätten wir den Rekursionsabschluss schon gefunden.
Also, wenn hier vorne die leere Liste steht,
dann setze die zweite Liste gleich der dritten.
Schwieriger wird es jetzt mit dem Fall hier unten und
da habe ich das ein bisschen umformuliert,
indem ich jetzt diese rekursive Notationsweise für Listen eingebaut habe.
Also, diese einer Liste kann ich auch schreiben,
es ist die einer Liste mit der leeren Liste als Restliste.
Und ich kann das Gleiche hier hinten machen,
dass ich sage, es ist die Liste,
die A enthält mit der Restliste BC
Und das mache ich für den fünften Fall auch,
dass ich sage, es ist die Liste,
die A enthält und eine Restliste hat,
die noch B und das Gleiche hier hinten,
eine Liste, die A enthält und eine Restliste hat,
die heißt BCD.
Und schließlich mit dem sechsten Fall analog.
Und wenn ich jetzt auf diese Daten hier gucke,
dann sehe ich es deutlicher, worin die Gemeinsamkeit besteht.
Die Gemeinsamkeit besteht offensichtlich darin, dass sich dieses Erste,
den Kopf dieser Liste, mit dem hier hinten unifizieren muss
oder gleichsetzen muss.
Und dass die hinter dem Listenseparator
die zweite Liste steht
Das ist dieser Fall erst mal. Also wenn hier
die leere Liste steht, dann übernimm den Kopf dieser Liste
und füge ihn mit dieser Liste zusammen.
Wenn hier hinten nicht die leere Liste steht,
dann mach das Gleiche mit den Restlisten.
Dann musst du also diese Liste hier mit dieser Liste zusammen,
sorry mit dieser Liste zusammenfügen.
Und damit hast du es reduziert auf diesen Fall hier oben.
Denn jetzt ist die Restliste dieser eingebetteten Liste hier wieder die leere Liste.
Und genau das Gleiche kann ich hier machen.
Ich kann jetzt hier das B abtrennen und hier
das B abtrennen und habe es damit auf den Fall
5a zurückgeführt
Ja, also die Vorschrift lautet offensichtlich,
nimm den Kopf in die Ergebnisliste und füge hier hinten
als Restliste das Verketten dieser Restliste mit dieser Liste ein.
Also Verkette-Dealliste BC mit der Liste.de zu dieser Restliste und hier oben verkette diese Restliste,
die nur noch B enthält mit dieser Liste und du kriegst den hinteren Teil.
Und hier verkette eine leere Liste mit dieser Liste BC
und du kriegst die Ergebnisliste BC. Ja,
und damit haben wir die beiden Klauseln für die Prädikatsdefinition zusammen.
Erster Fall war, wenn da vorne eine Lehre,
wenn das Ganze eine leere Liste ist,
auf der ersten Argumentstelle,
dann ist die gesuchte Ergebnisliste
gleich der zweiten Argumentstelle
Und ansonsten zerlege die erste Liste in Kopf- und Restliste,
übernimm den Kopf in die Restliste und sorge dafür,
dass diese Ergebnisliste hier hinten die Restliste,
die Verkettung dieser Restliste mit dieser Liste ist.
Also, Rufe, Reco, sief das Ganze auf,
verkette R1, das ist die Restliste hier vorn,
mit der zweiten Argumentstep-Position und du kriegst die Restliste raus,
die hier hinten benötigt wird.
Das war wieder etwas prozedural, etwas statischer, kann man das auch sehen. Also,
eine leere Liste verkettet mit einer Beliebigenliste ist die beliebige Liste und
die Verkettung einer Liste ist immer die Das Zusammenfügen aus der Restliste,
auf der ersten Argumentstelle mit der zweiten Argumentstelle ergänzt um den Kopf der ersten Argumentstelle.
So, damit haben wir eine Prädikatsdefinition und die können wir
jetzt natürlich wieder in die Tests schicken und jetzt haben wir erheblich mehr Tests als bisher immer,
denn jetzt haben wir drei Argumente,
die wir hier bedienen können und die wir instanziert oder nicht instanziert aufrufen können und sie wissen,
wir haben kombinatorische Explosionen hier.
Je mehr Argumente wir haben, umso mehr Testfälle bekommen wir.
Also alle können wir nicht durchexerzieren
Gehen wieder davon aus, die vollständig instanzierten können bei semi-automatisch testen.
Wir interessieren uns im Wesentlichen für die unterspezifizierten Aufrufe. Also die klassische Verwendungsweise,
wie es auch in der funktionalen Programmierung üblich wäre,
gib mir zwei Listen und ich werde dir sagen,
wie sie verkettet sind,
also die Liste ABC mit der Liste CDE verkettet gibt,
die Liste ABC.de.
Das scheint zu funktionieren. Wir können natürlich auch umgekehrt fragen,
mit was, nein,
welche Liste muss ich mit CDE verketten,
sodass ich die Liste ABC.de bekomme und ich kriege tatsächlich
das Anfangsstück heraus AB
Oder ich kann fragen,
was muss ich zu der Liste A, B hinzufügen,
damit ich die Liste A, B, C.de bekomme?
Und ich kriege also tatsächlich den hinteren Teil der Ergebnisliste.
Das sind die Rekursionsschema, da tat dafür. Und wir sehen an dieser Stelle,
dass hier auf der ersten Argumentstelle tatsächlich der rekursive Abstieg erfolgt,
also die Konsumtion der Eingabestruktur, bis sich bei der Lehrenliste angekommen bin.
Bei der Lehrenliste wird dann die Restliste, äh nein,
die Liste von der zweiten Argumentstelle unifiziert mit der dritten Argumentstelle.
Und ich habe beim Abstieg bereits diese Rekursions-,
diese Unifikationsgleichungskette aufgebaut. Das Ergebnis ist also der Kopf,
ich, als erstes abgetrennt habe, mit einer Restliste.
Im zweiten Schritt kenne ich die Restliste, denn es ist
der Kopf dieser Restliste, wieder verbunden mit einer Restliste.
Diese Restliste wird im dritten Schritt bestimmt, es ist der Kopf dieser Liste,
verkettet mit irgendetwas weiterem oder ergänzt um etwas weiteres.
Und dieses X3 ergibt sich dann im letzten Schritt zur leeren Liste.
Und damit sehen wir, dass bereits im Reco-Sieben-Abstieg klar war,
X ist die Liste, die A enthält,
ist die Liste, die B enthält, ist die Liste,
die C enthält und die nichts weiteres als diese enthalten wird.
Hier das Ganze umgekehrt und da sehen wir,
dass jetzt der Konsumption der Struktur auf der dritten Argumentstelle erfolgt.
Da sehen wir bereits, oho, auf der zweiten passiert ja gar nichts.
Die zweite wird ja immer nur durchgereicht. Da wird nie
drauf gerechnet. Das heißt, was ist der kritische Fall
dieser Definition? Den wir unbedingt testen müssen. Wir müssen
einen Blick dafür entwickeln, wo könnten die Probleme sein?
Die Probleme mit der Terminierung treten immer dann auf,
wenn ich nichts mehr konsumiere.
Wenn nirgendwo eine Struktur abgebaut wird,
was ist,
in welchem Fall passiert das
Also solange ich das erste Argument instanziert habe, passiert es offensichtlich.
Sobald ich das dritte Argument instanziert habe, passiert es auch.
Wenn es nicht passiert ist, wenn ich nur das zweite Argument instanziere,
dann können das erste und das dritte frei floten.
Und dann habe ich möglicherweise ein Terminierungsproblem. Also das ist
ein Fall, den ich beim Testen unbedingt berücksichtigen muss.
Denn dort könnte es möglicherweise sein,
dass ich die Kontrolle über die Rekursion verliere,
weil eben nicht mehr eine komplexe Struktur auf eine einfache
zurückgeführt wird,
sondern weil alles uninstanziert ist
Bleibt es, in beiden Fällen habe ich eben das Ergebnis bereits in der Hand,
wenn ich unten am Rekursionsabschluss angekommen bin. Das Ganze ist endrekursiv,
ist auch, das sollte man jetzt langsam mitbekommen haben,
nicht überraschend, wenn wir uns die Definition angucken.
Der rekursive Aufruf ist die letzte Klausel, das letzte Teilziel
in der rekursiven Klausel und damit sollte das Ganze tatsächlich endrekursiv sein.
Hier nochmal, der umgekehrte Fall, der ist also auch unkritisch,
wenn nicht das erste und das dritte Argument instanziert habe,
dann sieht man, dass der rekursive Abbau beiden Argumenten parallel erfolgt.
Ich baue also hier ein A ab,
hier ein Arp und habe dann diese Restliste und diese Restliste,
mit denen geht es weiter. Und das Ganze so lange,
bis ich hier vorhin bei der Lehrenliste angekommen bin und dann weiß ich,
dass die Ergebnisliste hier unten, die liste.de ist und achso,
ja, jetzt weiß ich hier unten,
die Restliste ist de und über die Co-Referenz wird es auf die zweite Argumentstelle übertragen.
Und von der weiß ich eben über die Co-Referenzgleichung,
dass das mein gesuchtes Ergebnis ist.
Also auch das ist endrekursiv. Hier sind also noch die
noch weitere Testfälle, der erste ist vielleicht ganz interessant.
Ich gebe diesmal nur die verkettete Liste vor und frage,
in welche Varianten lässt sich diese Liste zerlegen?
Das ist tatsächlich wieder eine Verwendungsweise, die häufig mal benötigt wird,
wenn ich eben verschiedene kombinatorische Varianten führe, die Zerlegung einer Liste brauche.
Und was wir hier sehen, ist tatsächlich,
dass wir all diese Varianten aufgezählt bekommen.
Also Zerlegung in leere Liste und Fünferliste,
Zerlegung in einer Liste und Viererliste und so weiter,
bis Zerlegung in Fünferliste und leere Liste
Und das ist der kritische Fall, den ich vorhin angesprochen habe.
Den können wir auch testen. Also wir geben jetzt
nur das Argument vor, auf dem rekursiv nichts konsumiert wird
und lassen das erste und das dritte Argument uninstanziert.
Wir sehen, auch hier kriegen wir, nein,
wir sehen nicht auch, sondern hier kriegen wir jetzt erstmalig unendlich viele Ergebnisse.
Hier, das sind ja auch endlich viele.
Nach diesen sechs Möglichkeiten bricht das Ganze ab.
Hier unten bricht es nicht ab. Dadurch,
der einzige Unterschied, und deshalb finde ich die Notation,
die jetzt verwendet wird, nicht sehr gut.
Der Einzelunterschied ist dieses Leerzeichen hier. Das sagt nämlich,
ich bin nicht das letzte Element,
sondern du hättest mich auch noch fragen können,
ob es weitere Elemente gibt.
Aber du hast es nicht getan
Nicht so glücklich, aber okay, wir müssen damit leben.
Das System ist so. Also, was kriegen wir hier?
Was muss ich zu CDE hinzufügen und was gibt es dann?
Oder was kann ich alles zu der Liste CDE davor setzen,
sodass sich eine Ergebnisliste ergibt?
Ja, ich kann natürlich die Lehreliste davor setzen,
dann ist die Ergebnisliste eben gleich der zweiten.
Ich kann eine einer Liste davor setzen,
dann wird dieses erste Element hier vorn auftauchen,
kann eine Zweierliste davor setzen, dann werden die zwei Elemente im Ergebnis auftauchen.
Ich kann das mit drei, mit vier,
mit fünf, mit sechs Elementen machen.
Also, hier Die Definition ist offensichtlich korrekt. Wir kriegen nicht
kein Terminierungsproblem. Das Ganze ist immer noch unter unserer Kontrolle.
Aber wir kriegen eben unendlich viele Ergebnisse in diesem Fall.
Das sind die Rekursionen, Schemata, die man sich da
nochmal angucken muss. Ich weiß jetzt nicht, inwieweit wir
die tatsächlich immer durchgehen müssen. Also wir sehen hier,
dass die Rekursive Abstieg eben auf der dritten Argumentposition passiert.
Und das gleichzeitig auf der ersten Argumentposition, das ist das X hier,
nämlich sukzessive, die lässt die Ergebnisliste aufgebaut wird.
Also A, das erste Element ist A,
ich kenne nur die Restliste noch nicht.
Das zweite Element ist B, aber die Restliste kenne ich noch nicht.
Das dritte Element ist C. Und jetzt kenne ich die Restliste,
das heißt, das ist das gewünschte Ergebnis
Und dann kann ich die, ach so,
das ist jetzt natürlich nur eine Variante, die ich hier habe.
Sorry, man muss natürlich jetzt hier, das ist nicht dargestellt,
insofern bin ich auch in Spoxhorn gelaufen.
Man muss hier natürlich jetzt immer all die Zwischenergebnisse werden schon immer ausgegeben.
Das ist jetzt hier nicht dargestellt. Als allererstes kriege ich
ja schon die Zerlegung in leere Liste plus die vollständige zweite Liste.
Die Zwischenergebnisse, die ich bekomme, sind hier also nicht explizit ausgewiesen,
sondern nur ein willkürlich gewählter Abbruch des Ganzen.
Ich habe jetzt hier also bei dem,
bei der Zerlegung in einer Dreierliste und in einer Zweierliste
aufgehört,
das Ganze aufzuschreiben
Gut, auch hier kann man sich leicht davon überzeugen,
dass das Ganze endrekursiv ist. Das ist also genauso,
wie wir es erwartet haben. Und wir können das Ganze,
und das ist dann wirklich der Lackmustest auf Richtungsunabhängigkeit,
wir können das Ganze aufrufen, vollständig unterspezifiziert.
Wir kriegen natürlich unter unendlich viele Ergebnisse,
die alle hochgradig unterspezifiziert sind, aber sie sind alle korrekt.
Also wenn ich die leere Liste mit irgendeiner beliebigen Liste vererkette,
dann ist das die beliebige Liste als Ergebnis.
Wenn ich eine einer Liste mit einer Beliebigenliste verkette,
dann ist es das eine Element dieser Liste verkettet mit der
Restliste,
die gleich der zweiten ist
Und die zwei Elemente, die hier vorne tauchen,
eben hier hinten auf, nur dass das alles uninstanzierte Variablen sind.
Eine sinnvolle Verwendung, dafür habe ich nicht. Aber es zeigt uns nur,
jawohl, wir haben das Ziel, Richtungsunabhängigkeit erreicht.
Das sehen wir an diesem Beispiel. Und dafür ist der
Test sinnvoll. Und wenn Sie es im Prolog suchen,
geben Sie erpennt ein, das ist in allen Prologsystemen eingebaut.
Ich habe es hier nur abgenannt,
weil ab ein Modewort ist natürlich, ja,
aber weil die Folien sonst überlaufen,
wenn ich das vollständige Wort hingeschrieben hätte.
So, und das Spiel können wir jetzt beliebig fortsetzen.
Für die Übung werden diese Definitionen hier einen Ausgangspunkt sein.
Ich kann jetzt beliebige Manipulationen über Listen machen,
allzu viele Möglichkeiten gibt es da nicht.
Wir gucken uns eine ganze Reihe von Beispielen an.
Mal schauen, wie weit wir kommen. Erste Möglichkeit ist,
ich möchte mir einen hinteren Teil einer Liste geben lassen.
Das kennt man aus der Lingvistik, der hintere Teil eines Wortes
ist ein Suffix und deshalb habe ich das ja auch ein Suffix genannt.
Ich möchte also Ja, das hintere Stück der Liste sehen und wir erinnern uns,
Zugriff zur Liste habe ich immer vom Listenkopf aus.
Das heißt also, den hinteren Listenteil kriege ich nicht auf triviale Art und Weise.
Ich muss erst mal in der Liste bis zu dem Element vorwärts gehen,
wo der hintere Teil beginnen sollte und mir dann die Restliste ausgeben.
Das ist die Idee dabei. Das heißt also,
im ersten Schritt stehe ich noch ganz am Anfang der Liste,
aber ich kann natürlich sagen, das, was ich dort vorfinde,
ja, das ist ein hinteres Stück der Liste.
Also, erste Klausel, das so fix einer Liste
ist wieder die Liste selbst
Das ist die erste Möglichkeit. Und ansonsten muss ich eben
die Restliste der Liste nehmen und von dieser Restliste wieder einen Sufix bilden.
So, und das gucken wir uns an,
wie es funktioniert. Also ich habe eine Liste gegeben,
ABC und ich möchte alle so fix in dieser Liste wissen und
ich kriege aufgezählt mit der Rekussionsabschluss, natürlich diese Liste,
die ich hier gegeben habe, ist ein mögliches Sufix.
Wenn ich nach Alternativen frage, wird diese Klausel benutzt,
die Klausel trennt den Kopf ab,
nimmt die Restliste und bestimmt mir so Fixe der Restliste
Und die Restliste ist jetzt die Liste BC,
also kriege ich als nächstes Ergebnis mit dem Rekussionsabschluss,
der einfach ja nur diese Chor-Referenz herstellt,
kriege ich das zweite Ergebnis.
Wenn ich wieder nach Alternativen frage,
wird wieder ein Kopf abgetrennt von der jetzt noch vorliegenden Restliste
und für die dann dadurch erhaltene Liste werden wir die so fixe ermittelt.
Also ich kriege jetzt noch die Liste C als mögliches Suffix.
Und wenn ich wieder nach Alternativen frage, wird wieder ein Kopf abgetrennt.
Und jetzt bin ich bei der Lehrenliste angekommen.
Und natürlich, das kennt keine Beschränkungen. Auch das,
die leere Liste hat einen Suffix, nämlich die leere Liste wieder selbst.
Das sagt uns der Rekussionsabschluss.
Also kriege ich jetzt diese vier so fixe einer Liste aufgezählt
Ich kann das Ganze wieder umgekehrt fragen. Ich kann sagen,
welche Listen haben die Liste AB als so fix
und wir können uns überzeugen, die Antworten sind sinnvoll.
Also die Liste AB oder die Liste, die irgendetwas,
gefolgt von A und B enthält oder eben die Liste,
die irgendetwas und noch etwas anderes, das sind zwei verschiedene Variablen hier,
gefolgt von A und B enthält.
Und das, auch das kann ich wieder unendlich lange fortsetzen,
indem ich aufzählen lasse, ja,
es ist eben die Fünferliste,
die erst drei unbekannte Variablen enthält und dann ein A und dann ein B und die Sechserliste,
die Siebenerliste und so weiter und so fort.
Also auch hier sehen wir, wir kriegen keine Probleme mit Terminierung hinein.
Die Ergebnisse sind Ad1 plausibel und A2 vollständig. Das,
was ich hier als Ergebnisse bekomme, beschreibt den Lösungsraum komplett.
Weitere Ergebnisse gibt es nicht. Symmetrisch zum Suffix gibt es das Präfix,
das Präfix ist der Anfangsteil einer Liste und wir müssen uns überlegen,
wie können wir den Anfangsteil einer Liste finden.
Wir müssen jetzt ja praktisch dafür sorgen,
dass wir immer Elemente vom Anfang der Liste nehmen,
sozusagen in der Hand halten und an einer beliebigen Stelle sagen,
so jetzt haben wir genug
An der beliebigen Stelle sagen wir jetzt also, das Prefix
ist die leere Liste. Das ist diese Klausel hier.
Egal, was immer in der zweiten Liste drinsteht,
das, was wir, wir wollen jetzt aufhören.
Wir wollen das Prefix sehen, was wir im aktuellen Regussionsstand haben.
Und der aktuelle Regussionsstand heißt, trenne einen Kopf der Liste ab,
milde das Prefix der Restliste und nimm diesen Kopf,
den du in der Hand hast, und baue ihn wieder davor.
Das ist das gleiche Muster,
was wir schon vorher gesehen hatten,
wenn wir uns
das Appent prozedural erklären
Das ist nämlich das, was hier steht.
Trenne einen Kopf ab, behalte ihn in der Hand,
nimm die Restliste und verkette es mit dieser zweiten Liste.
Dann nimmt das Ergebnis dieser Verkettung und setze den Kopf wieder davor.
Genau das Gleiche machen wir jetzt hier auch.
Wir bauen am Anfang der Liste Elemente ab,
behalten die auf dem Stack sozusagen und setzen sie in die Ergebnisliste davor.
Tatsächlich werden sie gar nicht auf dem,
landen sie nicht auf dem Stack, es ist endrekursiv,
die Ergebnisse werden gar nicht zwischengespeichert.
Es wird sofort diese Ergebnisliste hier aufgebaut. Also wir nehmen
das Element, was wir hier haben,
ist per Co-Referenz automatisch und unmittelbar in der Ergebnisliste drin
Und genau den gleichen Trick verwenden wir jetzt für das Prefix.
Ich wieder zurückschalte. Hat jemand die Definition im,
nicht heute, sondern in den vorhergehenden Vorlesungen schon mal irgendwo gesehen?
Ist aufgefallen, ist keinem aufgefallen? Diese Vorlesung dreht sich
ständig im Kreis. Da können Sie sich dran gewöhnen.
Wir kommen immer wieder an den gleichen Stellen vorbei,
wo wir schon zigmal waren.
Die Definition haben wir fast eins zu eins schon mal gehabt
an welcher Stelle
Genau, das ist die Addition der PA-Notzahlen, die da steht.
Nur, dass wir diesmal keine PA-Notzahlen haben, sondern Listen der Länge N.
Und eine Liste der Länge 3 verkettet man in einer Liste der Länge 4,
ist natürlich eine Liste der Länge 7. Wir können das benutzen,
um zu addieren.
Es ist nur ein bisschen Overkill, es ist nicht so erschrecklich,
aber wenn sie das vergleichen, wie weit müssen wir zurückgehen,
um das zu finden?
Manu,
irgendwo muss es
doch sein
Vielleicht sollte ich lieber das Inhaltsverzeichnis bemühen.
Hier ist es. Hier haben wir dieses Schema wieder.
Wenn das Ding bei der Lehrenliste angekommen ist, dann ist
die zweite Liste gleich der dritten. Wenn das Ding,
wenn ich hier einen Kopf abtrenne, mit anderen Worten,
ich nehme eine S-Schale weg,
da muss ich hier hinten die S-Schale wieder draufsetzen oder ich muss den Kopf da wieder hinzufügen.
Und das zweite Ding bleibt völlig unberührt. Das wird einfach
durchgereicht. Es ist genau exakt genau das gleiche Programmschema,
was wir hier haben.
Und wenn wir schon dabei sind,
das muss ja weiter hinten kommen
Haben Sie das Programm heute gesehen? Das ist die Länge
der Liste, die da steht. Nur,
dass wir hier die Tiefe der Peano-Struktur gezählt haben und
hier zählen wir die Tiefe der rekusiven Einbettung einer Liste.
Also wie viele Ebenen müssen wir runtergehen,
damit wir bei der Lehrenliste ankommen und hier haben wir gezählt,
wie viele Ebenen müssen wir runtergehen, damit wir bei der innen,
beim inneren Abschlusselement null der PA-Nutzer ankommen.
Es sind exakt genau die gleichen Rekursionsschemata, die dem Ganzen zugrunde liegen.
Nur haben wir hier eben die arratmetische Auswertungsumgebung. Achso,
nee, das bei der Länge müssen wir das ja auch machen.
Sorry, ich war jetzt im falschen Film.
Also, es ist genau dasselbe, was wir haben.
Und nach dem Schema hatten wir uns dann hier,
der Damen, nee, hat mir das nicht gemacht.
Ja, hier haben wir den endregusiven Fall und nach
dem gleichen Schema können sie auch länger einer Liste,
was wir heute behandelt haben, in eine entregusive Definition umwandeln.
Das geht also genau nach dem gleichen Prinzip.
Und das ist eines der wesentlichen Ziele dieser Veranstaltung,
dass sie solche Analogien sehen. Dass also wir bei PANO-Zahlen
immer nur über die Tiefe oder die Tiefe oder das Zusammenbasteln von eingebetteten,
einfachen Strukturen, also die nur eine Argumentstelle haben, geredet haben.
Und jetzt bei den Listen haben wir eben zwei Argumentstellen vorliegen.
Aber wir machen nichts anderes. Wir bauen jetzt solche eingebetteten
Strukturen wieder zusammen. Und damit machen wir nichts anderes,
als hätten wir S-Strukturen addiert. Oder wir ermitteln die Länge
einer S-Struktur. So, das war jetzt etwas zu weit.
Hier waren wir. Ja, also hier jetzt der gleiche Trick.
Wir trennen hier vorn ein Element ab und fügen es
sofort und unmittelbar hier in die Ergebnisstruktur ein.
Alles, was wir noch nicht kennen, ist die Restliste hier.
Und diese Restliste müssen wir berechnen, und das machen wir,
indem wir den rekusiven Aufruf machen,
dass wir für diese Restliste dP das Prefix berechnen oder das so berechnen,
dass P das Prefix dieser Reste der RS,
so rum muss man es eigentlich sagen.
Und das machen wir so lange, bis wir auf jeder
Ebene sagen wir, ja, wir brechen jetzt ab.
Also wir sagen bereits beim ersten Aufruf,
egal was immer hier hinten als Liste drinsteht,
die Lehre lässt es
ein Prefix davon
Im nächsten Schritt sagen wir, okay,
wir haben hier eine Liste, wir können hier Konsuktionen betreiben,
wir trennen das erste Element ab, wir haben eine Restliste,
die Restliste interessiert uns erstmal gar nicht, denn wir,
ja, das ist diese anonyme Vagable, und wir sagen,
dass das PrefixP eine leere Liste ist.
Also wir haben das abgetrennte Element A,
wir haben die leere Liste P, und wir setzen das
A vor die leere Liste und kriegen als zweites Ergebnis die eine Liste, die A enthält.
Und wenn wir das Ganze reklusiv aufrufen,
dann wird eben hier das nächste Element abgetrennt, das ist das B,
und es wird wieder ein Prefix der dann verbleibenden Restliste gebildet,
Und das Briefix ist als erstes wieder die leere Liste.
Das heißt, jetzt haben wir das A, was wir
schon oben abgetrennt haben, dann haben wir das B hineinunifiziert.
Und jetzt unifizieren wir ganz hinten noch wieder die leere Liste rein.
Und dann kriegen wir A, B als Liste als Ergebnis.
Und im dritten Schritt das gleiche nochmal. Wir trennen wieder
den Kopf der Liste ab. Das ist jetzt das C.
Wir ermitteln das Briefix der Restliste und kriegen die leere Liste wieder.
Und jetzt wissen wir schon, das Ergebnis wird Liste A,
Koma B, Koma C sein, nur was danach kommt,
wissen wir noch nicht.
Aber das wissen wir jetzt, das ist die leere Liste
Und wir wissen auch, weitere Ergebnisse wird es nicht geben.
Denn wenn wir das ganze Spiel jetzt nochmal betreiben wollen,
von der Lehrenliste, die wir jetzt hier hinten haben,
können wir nicht nochmal den Rekursionsschritt anwenden. Wir können zwar noch ein Ergebnis ermitteln,
das Prefix ist wieder leer, sodass wir also dieses Ergebnis ABC bekommen,
aber wir können von der Lehrenliste nicht mehr diesen Rekursionsschritt anwenden,
weil, und das hatte ich vorhin gesagt,
merken sie sich, dass Pattern hier wenigstens eine Liste gefordert wird,
die noch ein Element enthält.
Also die Unifikation auf die Lehreliste ist mit dieser Klausel
nicht mehr möglich, genauer gesagt hier,
sorry,
auf der zweiten Argumentstelle
Die Unifikation ist dort nicht möglich und deshalb bricht das Ganze
nach diesen vier Ergebnissen tatsächlich ab.
Beruf ist wieder umgekehrt auf. Wir sagen jetzt also,
für welche Listen ist die Liste AB ein Prefix und wir kriegen genau das,
was uns, nein, nicht genau das.
Wir machen jetzt die Konsumption auf der ersten Argumentstelle.
Wir trennen ein Element ab. Wir wollen das,
wir bauen ein Ergebnis auf, dass wir dieses erste Element
gleich in das Ergebnis übernehmen.
Wir rufen rekursiv des Briefix auf und wollen das Prefix der
Restliste wissen. Die Restliste ist jetzt aber noch nicht die
leere Liste
Oder das Prefix ist noch nicht die leere Liste.
Wir haben ja das Prefix gegeben, sondern dort ist noch
ein B drin. Das heißt, der Rekursionsabschluss ist noch
die einschlägig. Wir müssen noch ein Element abbauen,
nämlich das B und sofort wieder in die Ergebnisliste hineinunifizieren
und rufen das ganze Prefix wieder rekursiv auf.
Und jetzt ist diese Zweierliste vollständig abgearbeitet. Jetzt tritt der Rekursionsabschluss
in Kraft und hier hinten wird eine uninstanzierte Variable hineingesetzt.
Und das ist das Ding, was hier hinten steht.
Und das Ergebnis liest sich dann als, jawohl,
die Listen für die AB ein Prefix ist,
sind die Listen,
die A und B enthalten und danach eine beliebige Restliste
Auch ein korrektes Ergebnis. Vielleicht ein sinnloses Ergebnis,
aber ein korrektes. Drittes im Bunde ist,
sie sind Teillisten, die mache ich jetzt damit,
dass ich das mal ein bisschen kombiniere, indem ich sage,
ich mache einen Reco-Sieben-Abbau über die Liste,
indem ich immer Schritt für Schritt durch eine Liste durchgehe.
Und für die Restliste dann ein Präfix-Ermittler. Also was ich mache ist,
ich habe eine Liste, die A, B,
CD enthält, Ich lasse mir von dieser Liste alle Präfixe geben.
Also die leere Liste, die lässt du die A enthält,
die Liste, die A und so weiter enthält.
Dann, das ist, das ist das, was im Rekursionsabschluss passiert hier oben.
Gib mir oder Subliste einer Liste sind alle Präfixe der Liste.
Und im Rekursionsschritt baue ich die Liste eins ab.
Das heißt, ich betrachte jetzt nur noch diesen Teil der
Liste und lasse mir davon wieder alle Präfixe geben.
Und dann ist das gewünschte Ergebnis klar oder das Ergebnis,
Das Ergebnis, was sich ergibt, ist verständlich.
Ich kriege also als erstes die Präfixe der gesamten Liste.
Das ist Lehreliste, einer Liste, zweier Liste, Dreierliste.
Dann mache ich den nächsten Schritt. Ich trenne den Kopf
hier ab und lasse mir alle Präfixe der Restliste geben.
Das ist Lehreliste, E-B-C. Dann trenne ich das B ab
und habe noch die Liste, die C enthält und deren Präfixe sind C.
Es ist sie wieder, die leere Liste und C.
Und dann trenne ich nochmal das C ab,
habe die leere Liste und das präfixste Lehrenliste ist nur,
dass die leere Liste.
Da setze ich jetzt sozusagen Teillisten aus Präfixen von Restlisten
Und damit können sie in der Übung dann mal ein bisschen spielen.
Hier habe ich ihn mal aufgeschrieben und das ist die
letzte Amtshandlung für heute, dass wir hier ein ganzes,
einen ganzen Kalkül von Operatoren haben, die wechselseitig auseinander definierbar sind.
Ja, also wir haben etwa das Prädikat Member definiert.
Wir könnten jetzt Member genauso umschreiben,
indem wir unser Sublist-Prädikat nehmen und dass die Sublisten auf Einzelelemente festlegen,
auf einer Listen festlegen.
Und dann sind die Elemente dieser Einerlisten,
die hier genau die gesuchten Elemente der Liste
Und das können sie mal ausprobieren, in dem Satz auch
zum Beispiel in die Definition von Member irgendwo Subliste einsetzen.
Also nein, umgekehrt. Wenn man die Definitionen von Subliste und Member
gegeneinander hält, dann kann man sich mal überlegen,
warum ist das so, dass das äquivalent dazu ist.
Man kann genauso sagen, eine Subliste ist also die Kombination
eines Präfixes einer Liste und dann nimmt ein Suffix davon von dieser Liste.
Also trenne erst einen Kopf,
ein Kopfstück beliebiger Länge von der Liste ab und nimmt davon
dann den hinteren Teil
Ich kann es genauso umgekehrt machen. Nimm erst den hinteren Teil einer Liste,
also dieses Suffix und gib mir dann die Präfixe dieser Liste.
Das sind auch Teillisten. Ich kann es sogar so machen,
dass ich sage, ein Präfix ist offensichtlich eine Liste,
die ich mit irgendetwas verkette, sodass ich die Ergebnisliste erhalte.
Dann führe ich das Präfix auf Appent zurück. Ich kann
auch das Suffix zurückführen. Das ist also irgendetwas mit MS,
mit einem Suffix verkettet, ergibt die Liste. Oder ich kann,
und das geht so weiter, das können Sie sich jetzt hier mal angucken,
Das sind die drei Punkte, beliebig viele Varianten finden,
wie ich jetzt diese Prädikate, die wir hier haben,
also Member, Subliste, Prefix, so fixepent und so weiter,
auseinander definieren kann.
Das ist dann, der Punkt ist dann erreicht,
wo man mit solchen Prädikaten rechnen kann,
wo man sie beliebig wie in einem Bausteinkasten zusammensetzen kann und die gewünschten Ergebnisse erzeugen kann.
Und sie sollten sehen, dass das hier klappt,
dass dieses Rückführen von Prefix, Subfix und Subliste klappt,
liegt einzig und allein daran,
dass die Definition hier hinten richtungsunabhängig sind.
Wenn sie das nicht wehren, könnte man das
nicht mehr machen
Also insbesondere solche Sachen, dass die Reihenfolge,
ob ich erst ein Prefix und einen Suffix nehme oder erst
ein Suffix und dann ein Prefix, das hängt daran von ab,
dass sie ja in bliebiger Reihenfolge instanzierbar sind.
Okay, das soll ein erster kleiner Einblick gewesen sein in
die Welt der Listenprogrammierung. Wir machen damit nächste Woche natürlich
weiter und schauen uns ein paar Anwendungen auch an,
wofür wir das Gute gebrauchen können.
Und bis dahin wünsche ich Ihnen erstmal ein schönes Wochenende und
alles Gute