So, einen schönen guten Tag. Wir hatten uns
letzte Woche ja mit dem Interpreter für Skibben befasst.
Wir haben uns dann Beispiele für rekursive Funktionen angeguckt,
wo wir immer die Implementation in der Logikprogrammierung und die Implementation in der Funktionalprogrammierung mal gegeneinander gehalten haben,
um zu gucken, wo sind Gemeinsamkeiten, wo sind Unterschiede.
Und hatten insbesondere gesehen, dass die, dass man in der Logikprogrammierung Listenverarbeitungsprädikate,
endrekusiv machen kann, wo das in der funktionalen Programmierung nicht geht.
Das klassische Beispiel dafür ist die Appent-Funktion,
die eben in der funktionalen Programmierung nicht entrekusiv ist,
weil ich zum Schluss immer wieder noch das abgetrennte Kopfelement per Cons an die Liste hinzufügen muss,
während ich dies beim in der Logikprogrammierung schon beim dekosiven Abstieg machen kann.
Ich kann das gepunktete Paar schon aufbauen,
auch wenn ich die einzelnen Elemente noch gar nicht kenne.
Das ist also der zentrale Unterschied,
Und im Prinzip der Gewinn, den man dadurch kriegt,
dass man über die Unifikation eben eine richtungsunabhängige,
einen richtungsunabhängigen Operator für dieses Aufrufen von Prozeduren zur Verfügung hat.
Was wir jetzt noch machen müssen, ist die letzte Amtshandlung.
Das sind nämlich die Funktionen höherer Ordnung, uns mal anzugucken.
Ja, das wäre schön, wenn es ginge.
Hallo. Meine Maus ist weg
Nett. Sorry, ein technisches Problem.
Manchmal geht es damit wieder. Nein, geht nicht.
Jetzt komme ich nicht mal mehr in das Fenster.
Na, das sind Exquisite. Tja,
ich kann die nicht mal mehr abwürden,
also warten wir noch 20 Sekunden,
das ist ja automatisch ausgeschaltet wird.
Und dann fahren wir wieder hoch
Mhm. Das ist ein netter Spaß, ja.
Mich hat mir die Maus abzuklemmen. Das ist...
Ich komme nicht mehr. Die Cursor-Tasten geht noch.
Ah, jetzt, jetzt. Ah, zu spät.
Zu spät, du kriegst sie nicht mehr.
Oh, was war denn das? Gut, naja,
Versuch,
neues Glück
Musik Musik Musik Okay. In der Hoffnung, dass wir
jetzt mehr Glück haben. Also wir wollen über Funktionen höherer
Ordnung reden. Das sind entweder Funktionen,
die Funktionen als Argumente entgegennehmen und mit diesen Funktionen etwas machen
oder aber auch die Funktionen erzeugen,
sodass ich mit den erzeugten Funktionen dann andere Funktionen
wieder versorgen kann
Während man erstes noch ganz gut in der Logikprogrammierung nachvollziehen kann.
Es gibt auch im SWI-Prolog inzwischen Aggregatfunktionen,
denen ich in ein Aggregatprädikate, denen ich Funktionen übergeben kann.
Es ist allerdings noch nicht richtig ausgereift.
Deshalb habe ich es hier nicht mit berücksichtigt,
ist das Zweite ein Privileg der funktionalen Programmierung,
wo ich in der Logikprämierung keinen Pandanda dazu habe.
Weil ich eben das Konzept des anonymen Prädikats nicht habe.
Und ich habe es aus guten Gründen nicht.
Also ich muss in der Logikprogrammierung immer erst ein Prädikat in
die Datenbank eintragen,
ich sie benutzen kann
Habe ich eben das Konzept der der unbenannten Funktion zur Verfügung
und kann eine Funktion erzeugen und dann gleich wieder anwenden.
Also Beispiele für das erste sind die Familie der Map-Funktionen,
die es erlauben, Funktionen auf Listenelemente anzuwenden und mit
den Ergebnissen der Funktionsanwendung dann irgendwelche Dinge anzufangen.
Die gucken wir uns im Einzelnen noch an.
Und für das zweite sind es Funktionen,
die beispielsweise aus einer höherstelligen Funktion eine niedrigstelligere machen.
Das ist die Curry-Familie, Curry und R-Curry Oder aber
die einzelne Funktion hernehmen und zu einer neuen Funktion zusammenbauen,
das ist so etwas wie Compos.
So, der Urvater all solcher Funktionen höherer Ordnung,
das ist die Map-Funktion, die Map-Funktion dient dazu, eine Liste abzuarbeiten.
Und eine Funktion jeweils auf die Listenelemente anzuwenden.
Und die Ergebnisse der Funktionsanwendung werden aufgesammelt. Das heißt also,
man kann das jetzt redefinieren und sieht dadurch, wie es funktioniert.
Wir haben also gegeben eine Funktion, Wir sagen,
welche Funktion ist anzuwenden? Und wir geben die Liste auf
deren Elemente, die Funktion angewendet werden soll.
Wenn die Liste leer ist, dann ist auch die Ergebnisliste leer.
Es wird also beim Map immer, deshalb der Name,
immer jedes Listenelement in einen Funktionswert gemappt und die Funktionswerte werden wieder in der Liste gesammelt.
Das heißt, hier wird jetzt über das Cons die Ergebnisliste aufgesammelt und zwar immer,
indem die Funktion auf das erste Listenelement angewendet wird.
Und anschließend rekrusiv diese Map-Funktion wieder aufgerufen wird mit der gleichen Funktion,
die auf die Listenelemente angewendet werden soll,
aber mit der Restliste auf die Anwendung erfolgen muss
Und der interessante Punkt ist eben hier,
und da sehen wir jetzt die Standard-Auswertungsreihenfolge für Scream,
die erstmal alle Listen-Elemente auswertet und den Wert dieses Ausdrucks hier ermittelt.
Und ihr werdet dieses Ausdrucks, muss ein Funktionswert sein.
Er muss eine Funktionsdefinition sein. Das heißt also,
wir können beispielsweise uns eine Funktion Zero definieren,
die eben nur testet, ob die einzige Variable,
die wir hier haben, gleich null ist, wenn,
dann ja, dann ist es tru,
wenn nein, dann ist es fort.
Und wir können jetzt keinen Aufruf machen, indem wir
diese Funktion auf eine Liste anwenden und dann wird jetzt der
Der Wert dieses Atoms, nämlich die Funktionsdefinition,
die wir ihm hier gerade zugewiesen haben,
die wird jetzt aktiviert und auf die einzelnen Listenelemente angewendet.
Und dann gibt es eben eins, wird hier dran gebunden,
festgestellt, es ist nicht gleich null, also ist das erste Element.
Falls, null ist gleich null, also ist das Truh
und minus eins, ist ebenfalls nicht null.
Deshalb gibt es das dritte Element auch fort.
Hier sind wir, also hier ist der Fall,
dass wir tatsächlich eine Funktion benannt haben und dann anwenden.
Hier ist der Fall, dass wir jetzt tatsächlich eine anonyme Funktion darauf anwenden,
der wir keinen Namen gegeben haben,
wo wir eben nur sagen,
nimm diesen Funktionsausdruck hier und wende ihn auf die Elemente
dieser Liste an
Dieser Funktionsausdruck sagt eben nur, nimm jeweils das Element,
was du hier bekommst und multipliziere es mit zwei.
Also verdopple das Ganze und dadurch wird aus dieser Liste,
die lässt dir hinten. Das ist also der einfachste Fall
einer Funktion höherer Ordnung. Eine Variante davon ist die Filterfunktion,
Filter nimmt ebenfalls eine Liste und wendet eine,
die hier gegebene Testfunktion auf die Listenelemente an und sammelt aber
im Gegensatz zu Map nicht die Funktionswerte in der Ergebnisliste,
sondern die Elemente der originalen Liste und zwar nur dann,
wenn die Funktionsauswertung Truh ergibt
Dadurch wird im allgemeinen Fall die Ergebnisliste kürzer sein,
als die Liste, die ich hier übergebe.
Denn für alle Elemente, wo die Funktionsauswertung False ergibt,
die werden nicht in die Ergebnisliste aufgenommen.
Also haben wir hier das gleiche Prinzip. Wir arbeiten die Liste
rekursiv ab, bis wir bei der leeren Liste sind.
Wir haben einen IF-Klausel hier, die diese Testfunktion aufruft.
Es ist wieder der Wert dieser bayablen Test.
Und zwar muss dieser Wert eine Funktionsdefinition sein,
diese Funktionsdefinition wird auf das erste Listenelement angewendet,
Und anschließend wird jetzt nicht der Wert, der sich dabei ergibt,
sondern dieses erste Listen-Element wird genommen und mit dem rekursiven Aufruf von Filter zusammengebunden.
Für den Fall, dass dieser Test hier positiv ausgegangen ist.
Für den Fall, dass er negativ ausgegangen ist,
wird in die Ergebnisliste gar nichts aufgenommen,
sondern nur rekursiv aufgerufen mit der Restliste.
Das ist also der Gleichaufruf, den wir hier haben,
den haben wir hier auch im negativen Fall mit dem Unterschied,
dass eben hier das erste Listen-Element da vorgesetzt wird,
hier wird es
nicht davor gesetzt
Also wir nehmen wieder unsere Funktionen sehr roh und könnt jetzt eben die anwenden auf diese Listenelemente.
Das ist nicht null, also entfällt es,
das ist null, deshalb kommt es in die Ergebnisliste.
Das ist auch nicht null, deshalb entfällt es.
Diese Dreierliste wird reduziert auf all die Elemente, die diese Bedingung erfüllen.
Und der dritte, das dritte Element, achso,
nein, halt, Map können wir noch erweitern,
auf beliebig viele Listen. Das ist insofern ganz interessant,
weil wir hier mal ein Beispiel dafür sehen,
wie eine Funktion definiert wird, die beliebig viele Argumentpositionen hat,
Wenn wir hier, nämlich das gepunktete Paar,
was heißt, dies ist jetzt ein Funktionsaufruf-Pattern,
besteht aus dem Funktionsnamen einem ersten Argument.
Das ist nämlich die Funktion, die angewendet werden soll.
Und dann einer Restliste von beliebig vielen Elementen.
Und das sollten dann all die Listen sein,
auf die diese Funktion hier angewendet werden kann.
Und das müssen jetzt natürlich diese Funktionen,
muss so viele Stellen erlauben,
wie die Anzahl dieser Listen
hier ist
Was auch im Allgemeinen Fall kein Problem darstellt, weil diese Funktion auch wieder
beliebig viele beliebig Fälle stellen oder Feelingsfehler Argumente erlauben kann.
Gut, und die Idee ist dann eben ganz einfach,
dass ich... Ach so, die Abbruchbedingung ist noch,
ja, falls die... bin ich auf die Liste von Listen,
die Filterfunktion anwende mit der Funktion, ob die Listen leer sind.
Und das wieder eine leere Liste ergibt. Das heißt,
alle diese Listen von Listen sind vollständig abgearbeitet.
Das ist dieser Test hier
Dann gibt die leere Liste zurück, dann bist du fertig.
Das heißt, das Ganze wird so lange angewendet,
bis die alle Listen von Listen,
die hier hinten angegeben sind, unter Anwendung dieses leeren Listen,
Prädikats, eine leere Liste liefern.
Bitte? Denn beim einen, er ist der Computer rausgenommen,
das Ergebnis ist nicht militärisch wissen,
ob er im Kopf ist
Ach so, ja, ja, sorry, ja,
natürlich, danke. Es bleiben ja die drin,
wo der Test positiv ausgeht und nicht die, wo er negativ ausgeht.
Ja, klar. Also bis, wenn dieser Test positiv ausgeht,
dann ist es eine leere Liste und da kommt es nicht in die Ergebnisliste rein.
Und das heißt also, wenn eine der Listen noch nicht leer ist,
dann ist dieser Test, nee, wenn alle,
Moment nochmal, also wenn Denn eine davon, genau,
wenn eine davon leer ist, dann geht der Test negativ aus.
Tret nur dir Tatsache Rechnung, dass diese hier nicht zwangsweise
die gleiche Anzahl von Elementen haben müssen.
Gut, und ansonsten wird eben jetzt die Funktion,
die ich hier übergeben habe, mithilfe jetzt des normalen,
einfachen, im normalen, einfachen Map jeweils das erste Listenelement produziert.
Das heißt, ich erzeuge jetzt hier eine Liste,
die besteht aus allen ersten Listenelementen,
die hier oben übergeben worden sind
Auf diese Liste wende ich jetzt diese Funktion an.
Und das heißt, diese Funktion muss jetzt damit fertig werden,
dass sie eine Argumentliste bekommt, die beliebig lang sein kann.
Dafür muss ich natürlich als Nutzer, dafür muss ich recht tragen,
oder das muss ich sicherstellen, dass das gewährleistet ist.
Und ansonsten muss ich eben das Zwei-Sterne-Map
rekursiv aufrufen mit der gleichen Funktion wieder und allen Restlisten.
Und alle Restlisten kriege ich eben dadurch,
dass ich das normale Map mit der Funktion CDR über
die Liste von Listen
drüber laufen lasse
Auf die Art und Weise kann ich jetzt eben so etwas machen.
Hier ist eine Funktion, die beliebig viele Argumente zulässt, nämlich die Addition.
Ich gebe hier drei Listen beispielsweise an und es führt jetzt dazu,
dass hier jeweils die ersten Elemente miteinander addiert werden,
also eins plus zwei plus drei, ergibt dieses sechs,
dass die zweiten Elemente der Listen miteinander addiert,
werden zwei plus drei plus vier,
ergibt neun und dass die letzten Elemente auch miteinander addiert werden.
Das dritte Familienmitglied dieser Familie ist regiös
The used dient dazu, mithilfe einer Funktion, Listenelemente zu kombinieren,
indem ich einen Startwert vorgebe und dann jeweils das aktuelle Element
mit dem aktuellen Akkumulatorwert verknüpfe, mithilfe einer vorgegebenen Funktion.
Also wir gucken uns vielleicht erstmal das Beispiel an,
wir geben hier vor die Funktion Addition und wir geben
einen Startwert von null vor und das führt jetzt dazu,
dass die Liste der Reihe nach durchgegangen wird und jeweils immer der aktuelle Akkumulatorwert,
das ist hier Initial Null, mit dem aktuellen Listenelement addiert wird.
Also 0 plus 1 ist die erste Aktion, ergibt eins.
Im zweiten Schritt wird dann 2 plus 1 addiert im dritten.
Er macht 3 im dritten 3 plus 3 6 und so weiter,
bis wir eben die Summe dieser Flüstenelemente mit 15 haben.
Also das, was wir hier haben,
ist nichts anderes als eine elegante Form einsammelns draufzuschreiben.
Ohne dass ich gezwungen bin, ein rekrusives Muster anzugeben.
Auf die Art und Weise kann man Programme erheblich vereinfachen.
Gut, was machen wir hier unten?
Wir haben auch wieder eine Addition vorgegeben.
Wir geben hier
eine Funktion vor
Die, Quatsch, wir geben hier eine Liste vor,
die erzeugt wird durch diesen Map-Aufruf.
Und das ist das initiale Element. Und dieser Map-Aufruf nimmt
eine Funktion, das ist die, die hier steht,
und wendet sie auf diese Liste hier an.
Er bindet also immer die Elisten-Elemente der Reihe nach an dieses
X und liefert als Funktionswert eine Eins zurück.
Das heißt, egal was immer dieses Listen-Element hier ist,
es kriegt als Aufruf dieses Lamda-Ausdruckes einen Funktionswert von Eins,
Und macht daraus eine Liste.
Das heißt, der Aufruf dieses Map-Ausdrucks ist eine Liste,
die genauso viele einzeln enthält, wie hier Elemente drin sind.
Und anschließend mache ich einen Reduce, was die Listen-Elemente hier aufaddiert.
Also der erzeugten Liste. Und da dies eine Liste ist,
die lauter einzeln enthält, werden die einzeln auch verliert mit dem Anfangswert null.
Und ich kriege eine 3 heraus und habe somit nichts anderes
getan als eine etwas verklausulierte Definition für eine Längenfunktion,
die mir die Anzahl der Elemente liefert,
die in dieser Liste
hier enthalten sind
Gut, also letztes ist eine etwas,
das ist noch eine sinnvolle Verwendung des Ganzen,
das hier ist eine Verwendung, wo man sich fragt,
ob sie nicht eher dazu dient, zu.
Die Bedeutung von Programmen zu vernebeln, aber nichtsdestotrotz zeigt es,
wie man jetzt eben mit Funktionen spielen kann,
indem man sie nach beliebtem Miteinander kombiniert und jetzt eben hier durch dieses Kombinieren von einer Funktion,
die immer einen konstanten Wert liefert,
mit dieser Funktion höherer Ordnung und dieser Funktion höherer Ordnung
eine Funktionalität erzeugt,
die im Prinzip auf das Zählen der Elemente einer Liste hinausläuft
Funktionskomposition, das sind jetzt also Funktionen, die Funktionen produzieren,
sind als erstes die Curryfamilie, die Curryfamilie macht folgendes,
dass sie eine höherstellige Funktion nimmt.
Hier also eine zweistellige Funktion und eines ihrer Argumente
auf einen Wert setzt. Da wird man sich fragen,
also wer funktional programmiert schon mal hat, der weiß,
warum, aber normalerweise fragt man sich,
was soll der Quatsch eigentlich, warum will ich das?
Der Zweck der ganzen Angelegenheit ist,
damit ich dann diese vereinfachte Funktion wieder als Element in
einer Funktion höherer Errordnung
verwenden kann
Dort aufrufen kann. Also beispielsweise, wenn wir so etwas hatten,
wie dieses Väter hier, dann brauche ich eine Funktion,
die einstellig ist, die hier übergeben kann.
Die darf nur ein Argument haben. Wenn ich also jetzt etwas machen will,
beispielsweise wie nicht alle Zahlen, die gleich null,
sondern alle Zahlen, die größer null sind,
habe ich eine zweistellige Funktion größer als.
Ich muss diese größer als Funktion erstmal zu einer einstelligen Funktion
größer als null machen und dann kann ich diese größer
als null Funktion innerhalb eines solchen Filteraufrufes verwenden,
um damit Listen,
Elemente zu elektieren
Deshalb brauche ich also eine Möglichkeit, um höherstellige Funktionen niedrigstellig
zu machen. Und Curry und R-Karidinen eben genau dazu,
die Schnelligkeit einer Funktion, um eins zu verringern,
typische Anwendung ist eben aus einer zweistelligen, eine einstellige Funktion zu machen.
Und zwar sorgt Curry davor, dass dafür, dass dieses
hier angegebene konstante Element als erstes Argument dieser Funktion benutzt wird.
Das heißt also, das, was hier rauskommt,
ist eine Funktion, die testet, ob Nein, das,
was hier herauskommt, ist eine Funktion, die testet,
ob ein, ob das ihr übergebene Argument größer als er größer als 0 ist.
Ja, hier, das ist der symmetrische Fall dazu.
Da wird eben genau das andere Argument gebildet.
Das heißt, jetzt ist der Test eben darauf,
ob es kleiner als eins ist und deshalb kommt in diesem Falle.
Kleiner als World ist, sorry,
und deshalb kommt in diesem Fall für die Eins
ein Fords heraus
Und hier sind die Implementationen angegeben. Wir sehen dann also,
dass die Curry-Funktion jeweils eine Funktion nimmt und ein Argument,
das ist nämlich genau dieses Argument,
was ich binden will, dieses hier,
das ist die Funktion, das ist dieses Argument.
Und sie produziert einen Lamda-Ausdruck. Das heißt, das Ergebnis
der Funktionsanwendung ist eine neue Funktion. Und diese Funktion kann ich
sofort wieder in anderen Funktionen höherer Ordnung benutzen, um irgendwelche Berechnungsresultate hervorzurufen.
Und der Lamde-Ausdruck, der hier erzeugt wird,
ist nichts anderes als ein Aufruf dieser Funktion,
mit dem hier spezifizierten Argument, und das ist eben Curry,
dann wird es das erste Argument.
Und dem neuen Argument, was ich da erzeugt habe.
Und hier ist der umgekehrte Fall, das Argument und neues
Argument, hier miteinander vertauscht sind.
So, die Möglichkeiten, die wir haben, sind sehr vielfältig.
Wir können also jetzt beispielsweise über Curry plus 1 ein Intermentfunktionen definieren,
die ein gegebenes X immer um 1 erhöht.
Wir können mit Curry geteilt durch eins den Rezit Hochwert berechnen lassen.
Wir können mit Curry geteilt durch zwei die Sache halbieren,
mit multiplizieren, mit zwei verdoppeln. Wir können Quadrate bilden,
indem wir eben jetzt dieses Argument hier auf zwei,
das Argument innerhalb dieser Exponentialfunktion auf zwei Sätzen.
Wir können mit Curry minus null das Vorzeichen umkehren.
Wir können Curry gleich null einen Test auf null implementieren und so weiter und so fort.
Und das ist der typische Verwendungskontext. Wir nehmen jetzt eine,
das ist das, was sie angekündigt hatte bereits,
wir nehmen eine zweistellige Funktion,
binden eines ihrer Argumente auf null und und kriegen damit einen Test heraus,
der uns überprüft, ob eine Zahl größer als,
ist das richtig, ich bringe es auch immer durcheinander mit dem Curry und RKW,
ist Hirn verdrehen.
Achso, ja. Also wir haben ja immer die Kombination,
Curry und RCurry und wir haben die Kombination kleiner als größer als.
Da muss man also höllisch auffassen. Im Endeffekt kommt hier
ein Test größer als Null heraus und der wird jetzt
eben per Map auf diese Liste hier angewendet und führt dann
zu dieser Sammlung von Wahrheitswerten
Gut, das Ganze ist allgemeiner als hier dargestellt,
also es funktioniert auch, funktioniert typischerweise auch mit beliebig vielen Argumenten,
aber das ist die wohl häufigste Verwendungsweise.
Wir können Funktionen hernehmen und miteinander kombinieren und ausgegebenen Funktionen eine neue Funktion uns bauen lassen,
Kompost ist ein solches Beispiel,
das nimmt zwei Funktionen als Argumente und baut daraus eine neue Funktion,
also ein Landerausdruck,
der dann als Wert dieser von Postfunktion zurückgeliefert wird und das ist nichts anderes als die Anwendung von Funktion 1,
auf den Funktionswert der Anwendung von Funktion 2 auf den gegebenen Argumentwert.
Und auf die Art und Weise kann ich jetzt eben K und CDR miteinander verknüpfen,
indem erst der CDR angewendet wird, also die Liste BC,
ermittelt wird und dann anschließend auf diesen Funktionswert nochmal die Funktion K angewendet wird,
sodass sich das erste Element der Liste BC bekomme.
Auf die Art und Weise kann ich jetzt also
aus einfachen Funktionen zahlreiche komplexere Funktionen bilden
Familie, DRK- und CDR-Funktionen,
die sind zum größten Teil in den Sprachen der Lisp-Familie vordefiniert,
dass man eben das typischerweise nicht braucht, sondern direkt verwenden kann.
Irgendjemand macht sich an einen Spaß hier. Also so etwas
wie Compos K und CDR ist eingebaut als Funktion Katar.
Und da gibt es je nach Implementation immer eine ganze Familie,
also so etwas wie Das ist Compose Car,
mit K oder Kompost, CDR, mit CDR.
Das ist also sozusagen zwei Elemente einer Liste abgetrennt und dann die Restliste gegeben.
Also das, was wir in Prolog etwa so schreiben würden.
Trainer 2 Kopfelemente ab und gib mir die Restliste davon.
Dies hier wäre in Prolog
Dieses Pattern, ja, und Cadra wäre eben der K vom CDR, das ist...
Und die sind also sozusagen in den meisten Sprachen,
also den meisten Implementierungen vor, definiert,
aber man kann sich auf die Art und Weise auf Beliebe hier neue Funktionen zusammenbauen.
Es gibt weitere Möglichkeiten dieser Art conjoin,
also für Herdy Cards Funktion, die Wahrheitswerte liefern,
wahr, wenn alle die Einzelfunktionen wahr sind, distroyen,
wenn wenigstens eins wahr ist.
Und always ist eine Funktion mit konstantem Wert. Kann man
auf die gleiche Weise verwenden, wie wir das vorhin schon gemacht hatten.
Da hat man nämlich eine Funktion, die immer den gleichen Funktionswert zurückgegeben hat.
Hier können wir es direkt auch so hinschreiben,
sodass also dieser Ausdruck hier wieder nichts anderes macht,
als die Listenelemente herzunehmen, für jedes Listenelement die Funktion always aufruft,
die nur einen konstanten Wert zurückliefert und dann werden die Einzelnen,
die dabei erzeugt werden, hier aufaddiert, mithilfe von diesem Reduce,
sodass insgesamt wieder die Länge der Liste herauskommt.
Gut, hier sind mal Beispiele angegeben, wo man sehen kann,
dass einerseits auf die Art und Weise sehr schöne,
gut lesbare Programme entstehen, dass man es andererseits aber auch wieder übertreiben kann,
wenn man zu viel Gebrauch davon macht.
Man hat jetzt unglaublich viele Möglichkeiten,
einfache Funktionen zur komplexeren zusammenzubauen, auf verschiedenste Art und Weise.
Und wenn man das zu weit treibt,
dann wird es wieder unverständlicher,
was dabei herauskommt
Also ein schönes Beispiel ist sicherlich dieser Definition hier des Mittelwertes einer Liste von Werten.
Ich möchte einfach den Mittelwert berechnen und den Mittelwert berechnen,
indem ich die Werte aufsummiere und durch ihre Anzahl teile.
Und beides kann ich eben mithilfe solcher Reduce oft Ausdrücke berechnen.
Also ich nehme den Anfangswert null und addiere alle Werte,
die in der Liste sind, auf diesen Anfangswert drauf.
Das ist diese erste Funktionsaufruf hier oder der erste Aufruf von Reduce.
Und im zweiten Fall mache ich eben wieder diese Längenermittlung.
Also ich habe eine Funktion, die immer konstant 1 liefert.
Das heißt, ich kriege eine Zahl raus, die so groß ist
wie die Anzahl der Werte, die ich hier habe.
Ich habe also die Summe, ich habe die Anzahl
der Werte und ich dividiere das eine durch das andere.
Das ist also eine Möglichkeit, den Mittelwert zu berechnen.
Wenn ich jetzt die Varianz berechne,
dann muss ich ja immer die Differenz der einzelnen Werte zu dem Mittelwert bilden,
dann mache ich hier eben eine Let-Umgebung,
indem ich eine lokale Variable auf dem Mittelwert dieser hier gegebenen Werte setze.
Der Mittelwert, wie er berechnet und steht hier oben.
Und anschließend muss ich den Mittelwert bilden von Den Quadraten der Differenzen.
Die Differenzen berechne ich, indem ich hier über R-Curry die Minusfunktion mit meinem Mittelwert instanziiere.
Damit berechne ich die Differenz zwischen dem aktuellen Wert,
den ich hier gerade anfasse und dem Mittelwert,
den ich hier vorher berechnet habe.
In diesem Fall ist es egal, ob ich Curry oder
R-Curry verwende.
Warum? Ja
Genau, also hier das Ganze ist eingebettet in dieses Quadrieren
dann und damit ist es egal, wie das Vorzeichen ist.
Das kann ich also so rum, die Differenz bilden,
aber auch andersrum, die Differenz bilden.
Also hier wird die Differenz zum Mittelwert gebildet und dann wird wieder
ein anderes Map über diese Liste der Differenzen drüber geschoben,
die das Ganze quadriert.
Und dann muss ich zum Schluss nochmal den Mittelwert davon bilden,
um die Variante zu berechnen. Hier unten ist eine Variante davon,
mit Kompos angegeben und da wird es schon etwas haariger.
Also hier ist nichts anderes. Wir haben hier gesehen bei
Kompos, dass die beiden Funktionen hintereinander aufgerufen werden
Ja, hier war es schon. Also zuerst wird Funktion 2 angewendet,
dann wird Funktion 1 auf die Ergebnisse der Anwendung von Funktion 1 angewendet.
Und das sehen wir eben hier, zuerst wird Funktion 2
angewendet und Funktion 2 war nämlich genau dieser innere Teil hier.
Das ist genau das Gleiche, wie das hier.
Und Funktion 1 ist eben die Quadratfunktion. Das heißt,
es wird zuerst wieder die Differenz gebildet und anschließend die Quadratfunktion auf die einzelnen Elemente angewendet.
Das Einzige, was hier jetzt hinzukommt, ist,
dass ich für dieses Kompos Eine zweistellige Funktion wieder einstellig machen muss.
Denn dieses Map ist ja eine zweistellige Funktion.
Das will eine Funktion haben und das will die Werte haben,
mit deren ich die Funktion aufrufen kann.
Und da muss ich jetzt erstmal dieses Map über Curry wieder einstellig machen,
damit ich jetzt mit Kompost verwenden kann und dann darauf tatsächlich auch die Werte hier anwenden kann.
Denn ich muss es ja auf jeden einzelnen Wert anwenden.
Gut, also das sind Beispiele dafür, wie man jetzt
durch spielerisches Kombinieren solcher Funktionen höherer Ordnung interessante Effekte erzielen kann.
Weiter will ich es hier auch nicht treiben. Alles andere
muss man sich dann bei Frau Dröschler in der funktionalen Programmierung
zu Gemüte führen. Wir haben noch erfreulich viel Zeit,
sodass ich jetzt also einen großen Sprung mache,
oder sagen wir mal so, um das ordnungsgemäß abzuschließen.
Gibt es noch Fragen zu diesem funktionalen Teil? Die können wir
gerne jetzt diskutieren. Damit ist die Vorlesung noch nicht am Ende.
Ich habe mir nämlich was aufgespart, das haben Sie ja mitgekriegt,
dass ich zwischendurch mal einen größeren Teil übersprungen habe.
Das habe ich aus dem Grund gemacht, weil ich gern
wie es abgeschlossen haben möchte, eh die letzte Übung stattfindet.
Und in der letzten Übung ist ja die funktionale Programmierung dran.
Deshalb wollte ich das also erst mal gemacht haben.
Und mache jetzt noch Themen aus der,
oder sagen wir eigentlich nur ein Thema aus der Logikprogrammierung.
Und zwar, man hebt sich ja immer das Schönste zum Schluss auf.
Es ist tatsächlich der schönste Teil der Logikprogrammierung. Das ist
der Teil, wo man anfängt, langsam zu grübeln.
Was passiert da eigentlich noch? Wo es also die
klassischen Begriffe von Programmierung so überhaupt nicht mehr anwendbar sind.
Also der klassische Begriff von Programmierung heißt immer, dass ich sage,
Was wird wie, in welcher Reihenfolge eigentlich gemacht?
Und das ist der Bereich der Differenzlisten. Wir hatten ja
schon uns... Oder das hatte ich ja vorhin gerade diskutiert. Der Grund,
weshalb ich in der Logikprogrammierung das Appent Entricossiv programmieren kann,
ist die Tatsache, dass ich mit Listen operieren kann,
die noch valiabel enthalten und ihren Verb zu einem späteren Zeitpunkt
zugewiesen bekommen
Die Liste aber bereits existiert. Und diese Eigenschaft machen wir uns jetzt zu Nutzer.
Auf dieser Basis können wir also mit Datenstrukturen arbeiten,
die erst partiell instanziert sind, die also noch vajable enthalten,
deren Wert möglicherweise für immer unbekannt bleibt und dann im Ergebnis auftaucht.
Solche Beispiele haben wir gesehen. Oder aber deren Wert
im weiteren Gang der Berechnung irgendwann nochmal instanziert wird.
Und dann im Endergebnis das gewünschte Berechnungsresultat zeigt
Und das nenne ich hier aktive Datenstrukturen. Also,
und das ist ein Privileg der Logikprogrammierung,
das gibt es in keinem anderen Paradigma, dass ich Daten habe,
die eigentlich noch offene Elemente enthalten, aktive Elemente enthalten.
Denn wir werden sehen, dass wir auf die Art und Weise
eine prozedurale Bedeutung für Datenstrukturen bekommen. Der einfachste Fall,
den haben wir schon gesehen,
ich kann jederzeit natürlich ein Prädikat definieren, was so etwas macht wie,
Ja, in jeder anderen Programmiersprache wäre das ein Prozedur,
die ich schreiben muss, die eben einer vajabeln,
einen anderen Wert zuweist.
In diesem Fall ist durch die Co-Referenz bereits eine gewisse prozedurale
Semantik gegeben, die hier aber noch sehr rudimentär ist.
Und die Idee, wenn wir jetzt weiter aufbauen.
Es wird, das kann ich schon vorher darauf hinauslaufen,
da können Sie schon mal anfangen, drüber nachzudenken,
was da eigentlich passiert.
Es wird darauf hinauslaufen, dass das
unsere Appent-Definition ist
Ja, also sie können schon mal im Hinterkopf hervorrufen,
wie haben wir ein Appent bisher definiert und wir werden es dann so definieren können.
Es ist nur noch ein Fakt. Insbesondere verschwendet die Rekursion
und wir müssen rauskriegen, warum sie verschwendet. Gut,
wir führen als erstes den Begriff der offenen Liste ein.
Wir haben schon sehr, sehr oft mit offenen Listen gearbeitet,
ohne das, was sie richtig wussten.
Und den erweitern wir dann zur Differenzlisten. Und wenn man auch Zeit haben,
können wir nächste Woche die Hauptanwendung davon angucken, das sind nämlich,
definiert Claus Grimmers, eine Erweiterung der kontextfreien Grammatiken,
mit denen Ifgun beliebige Grammatiken schreiben kann,
beispielsweise für den Compilerbau oder für die Verarbeitung natürlicher Sprache.
Also, was wir klassischerweise in Programmiersprachen haben, ist so etwas wie dynamische Datenstrukturen.
Listen sind ein typisches Beispiel dafür. Bei Listen kann ich eben,
da ich einen großen, ein großes Reservoir A,
ein Listenzellen zur Verfügung habe,
im Prinzip immer eine Liste weiter ergänzen, indem ich,
und zwar von Form, zusätzliche Elemente an die Liste hinzufüge.
Ich brauche nichts anderes als eine neue Kunstzelle und baue daraus
aus einer gegebenen Liste und einem neuen Element
eine Ergebnisliste zusammen
Im Gegensatz zu Ares, wo ich also vorher immer sagen muss,
ich will 10.000 Elemente haben und dafür wird der Speicherplatz aluziiert.
Da sind eben Listen dynamisch erweiterbar. Das unterscheidet sich von
dem Begriff der aktiven Datenstruktur, wie ich ihn hier verwende.
Hier geht es also nicht darum, dass ich die erstmal
die Sachen erweitern kann. Da sind klassische Listen,
sowohl in der funktionalen Programmierung als auch in der Logikprogrammierung auch.
Hier geht es eben darum, dass ich vaiable Elemente enthalte,
in einer Datenstruktur vajabe Elemente habe,
die ich noch mit einem Wert versehen kann
Und das heißt, ich rede über mir derzeit möglicherweise noch unbekannte Elemente.
Vielleicht werde ich Ihre Identität noch kennenlernen, möglicherweise aber auch nicht.
Also, es geht hier um unvollständige Datenstrukturen oder eben auch um offene Datenstrukturen.
Wenn wir uns klassische Listen angucken, die klassische Verwendung oder der
typische Fall der Verwendungsweise einer Liste ist ja als Kellerspeicher.
Das kennen Sie alle, Kellersspeicher haben ja immer die Eigenschaft,
dass sich Form oder oben auf den Kellerspeicher etwas draufpacken kann.
Ich kann das von oben wieder runternehmen und ich kann irgendwann
mal feststellen, ob der Kellerspeicher leer ist.
Mehr Operationen gibt es ja dafür nicht. Das ist ja
die typische Anwendungsbeispiel für allgebranische Datenstrukturen, wo ich eben Methoden definiere,
mit denen ich auf einer solchen Datenstruktur arbeiten kann.
Wir hatten ja schon gesehen,
dass wir diese Sicht der allgebranischen Datenstrukturen oder der Methoden hier nicht so richtig anwenden können,
weil Dinge, die in der objektorientierten Programmierung in unterschiedlichen Methoden ausgedrückt werden,
hier dank der Unifikation in ein und derselben Methode zum Ausdruck kommen.
Also wir können beispielsweise ein Prädikat definieren,
was uns sämtliche Formen von Operationen mit einem Stack enthält.
Also, das steht hier. Es ist immer ein dreistelliges
Prädikat, was ich brauche. Ich habe einen gegebenen Stack.
Ich mache irgendetwas damit und ich kriege einen neuen Stack heraus.
Dass irgendetwas machen damit heißt, ich packe ein neues Element
drauf oder ich nehme ein da existierendes Element herunter.
Und das ist die gesamte Definition, die ich brauche. Also,
wenn ich ein Element habe und hier einen alten Stack habe,
dann kann ich mit dieser,
mit diesem Prädikat hier das Element auf den Stack draufbringen
Ich kann es natürlich auch umgekehrt in anderen Instanzierungsvarianten verwenden.
Ich kann eben hier meinen alten Stack angeben und hier den neuen Stack entnehmen.
Das ist nur eine Frage, wie ich das Ding aufrufe.
Und dann ist es das Pop. Und ich kann es verwenden,
um zu testen, ob der Stack leer ist oder nicht.
Also, der Push ist der Aufruf. Ich gebe hier
einen alten Stack vor. Ich möchte den neuen Stack hier
haben. Und das Element auf diesen Stack draufschreiben und dann
sehe ich, es ist die Liste eingetragen worden.
Wenn ich jetzt diesen Stack hier nehme und ein weiteres Element
darauf packe, dann wird es eben vorne dran geschrieben
Das ist keine Magie. Und wir nehmen das gleiche Prädikat
jetzt und instanzieren sich nur anders und sagen,
hier ist mein alter Stack und hier kommt der neue Stack heraus.
Und dann ist es ein Pop. Dann kriege ich eben
das herausgenommene Element und ich kriege den neuen Stack,
der ist um eins verringert worden.
Und wenn ich das nochmal mache, dann kriege ich eben
hier den leeren Stack heraus und das letzte Element ist mir übergeben worden.
Und ich kann eben das gleiche Ding dafür verwenden,
dass ich einen Test auf Elemente mache,
enthält der Stack noch Elemente,
typischerweise implementiert man immer den umgekehrten Test,
ist der Stack bereit
Leer, aber das ist ja logisch äquivalent,
das ist ja nur eine Negation dazwischen. Und hier ist es
natürlich viel einfacher zu testen, enthält er noch Elemente.
Und das kann ich eben ganz einfach machen,
indem ich dieses gleiche Prädikat wieder aufrufe mit einem Stack.
Und wenn da noch Elemente drin sind, kommt Truhe heraus,
wenn da keine Elemente mehr drin sind, kommt Fouls heraus.
Brauchen wir uns nur die Definition anzugucken, keine Magie dabei.
Wir fordern hier hinten eine Liste, die mindestens auch
ein Element enthält. Wenn wir die nicht kriegen,
dann kommt Fouls heraus und dann ist der Stack eben leer
Also wir sehen das, was in der Objektorientierten Programmierung drei
verschiedene Methoden waren, mit denen wir auf den Stacktoo greifen.
Das ist hier ein und dasselbe Prädikat, was wir nur in
unterschiedlichen Instanziierungsvarianten aufrufen. Das ist alles in einem versteckt.
Das kann man also für den Stack sehr schön machen.
Das passt wunderbar in die Welt der Listenprogrammierung hinein.
Das passt wunderbar zusammen mit den Begriffen der Logikprogrammierung,
wo ich jetzt eben durch Informationsanreicherung Datenstrukturen aufbauen oder im
umgekehrten Fall auch wieder abbauen kann
Problematisch wird das im Falle der Warteschlange. Im Falle der
Warteschlange wollen wir hier jetzt nicht von vorne Elemente hinzufügen,
sondern wir wollen es von hinten machen.
Also immer schön hinten anstellen, ist ja die Stichwort.
Also wenn wir etwas in die Warteschlange hineinsetzen,
dann muss es am letzten Ende passieren,
wenn wir etwas herausnehmen, dann ist es am besten Anfang.
Also der zweite Fall sollte kein Problem darstellen,
der erste Fall schon. Und was wesentlich ist, ist,
dass Einfügen und Wegstreichen von Elementen jetzt keine Umkehroperation mehr sind voneinander.
Beim Stack sind es Umkehroperationen und deshalb passt das
so schön in das Schema dieses einen Fakts hinein
Ich werde das hier also nicht mehr in einem Prädikat realisieren müssen,
sondern ich brauch separate Prädikate für das Einfügen und das Weglöschen aus der Warteschlange.
Also hier das Einfügen in die Warteschlange, wenn denn
die Altwarteschlange leer ist, dann wird es da hineingesetzt.
Ansonsten kommt es eben an das Ende der Warteschlange hinten dran. Das heißt,
ich muss die alte Warteschlange rekursiv abarbeiten,
bis ich irgendwann das letzte Element in der Hand habe oder die Lehreliste in der Hand habe,
dann kann ich das hinzufügen und das Rekursionspattern kennen wir über die Rekursion,
wird also jetzt die ganze Liste rekonstruiert,
weil ich immer den Kopf der Liste hier wieder als erstes
Element einfüge und die Restliste über den rekursiven Aufruf bestimmen lasse
Auf die Art und Weise wird also das eh immer als
letztes Element in die Warteschlange eingeführt. Sehen wir hier,
wenn die leer ist, dann kriegen wir eben eine neue Warteschlange heraus,
die das Element enthält. Wenn da bereits was drinsteht,
dann kommt das neue Element eben hier ans Ende daran.
Jetzt brauchen wir den Gegenteil, den anderen Fall,
der ist trivial,
da ist es wieder ein einziger Fakt, weil wir,
das hatten wir schon beim Stack,
ja nichts anderes machen müssen als das erste Element aus dem Sack herauszunehmen und zu übergeben.
Das Problem dieser Implementierung ist jetzt, dass sich beim Einfügen,
das wissen wir auch,
wenn ich als Ende einer Liste etwas einfügen will,
muss ich eben immer
die gesamte Liste kopieren
Und das ist ressourcenfressend. Die Frage ist, kann ich
das vermeiden? Und da würde man naiverweise sagen, nein,
kann ich nicht vermeiden, aber genau da wollen wir hin.
Wir wollen dorthin, dass ich am Listenende etwas einfügen kann,
ohne dass ich die ganze Liste kopieren muss.
Und das Problem, was wir hier haben, ist eben,
dass der Uninstanziert Aufruf dieses eines Arguments immer automatisch ein Listenkonstruktor darstellt.
Also in dem Augenblick, wo ich jetzt hier Hier, das uninteressiert.
Aufrufe heißt es automatisch, konstruiere mir dort eine Liste.
Also nimm eine Kunstzelle und baue daraus eine Liste.
Auch wenn du das Ding hier noch gar nicht kennst,
du kannst ja schon mal die Kunstzelle aufbauen.
Da steht eben noch eine Variable drin. Gut,
und das ist mit Verbrauch und Freischlächerzellen verbunden und aus Effizienzgründen
nicht so ganz nett. Man kann es anders machen,
man kann eine solche Liste als offene Datenstruktur implementieren. Das,
was wir klassischerweise hier als Listenrepräsentation für die Warteschlange haben,
ersetzen wir jetzt durch das, was hier steht.
Also die Lehreliste, fangen wir mal an,
das ist am wenigsten plausibel,
ist frei
Machen es so, dass wir sagen, ja,
hier diese Liste AD, die Warteschlange,
die AB und C enthält, soll nach rechts erweiterbar sein.
Das heißt also, hier ist eine Variable vorgesehen,
die ich noch durch zusätzliche Listenelemente instanziieren kann.
Und auf die Art und Weise kann ich jetzt die Liste nach rechts ergänzen,
ohne dass sich das, was davor steht, kopieren muss.
Also, das Listenende wird jetzt nicht mehr dadurch charakterisiert,
dass ich hier bei der leeren Liste ankomme,
wenn ich das rekursiv abbaue,
sondern das Listenende wird dadurch charakterisiert,
dass ich hierbei in der Restliste ankomme,
die noch
eine Variable ist
Also ich muss jetzt testen, ist die Reste,
ist eine Variable, dann bin ich am letzten Ende angekommen.
Und da kann ich etwas hineinunifizieren.
Und das gilt also für all diese Fälle.
Und wir müssen uns irgendetwas überlegen für die leere Warteschlange.
Und ich habe eben hier einfach eine Liste genommen oder besser gesagt
ein gepunktetes Paar, wo beide Elemente noch bei Yablen sind.
Also der Test auf die leere Liste ist jetzt hier immer,
ist der Kar der Liste.
Sorry, funktionale Terminologie ist das erste Element der Liste leer
und ist die Restliste ebenfalls leer.
Nicht leer. Sind es noch Vayablen? Im Gegensatz hier
dazu, hier ist das erste Element der Liste bereits
eine Konstante
Da ist schon mal was instanziert worden.
Also ein Element in die Warteschlange eingefügt worden. Gut,
also das Entscheidende ist, der Listenrest ist eine uninstanzierte Vagabel.
Ich kann also immer in allen Fällen hier hinten etwas hineinunifizieren,
wenn ich das denn möchte, also neue Elemente in die Warteschlange
einfügen und es muss die Listenendedetektion in den beiden Fällen machen.
Die beiden Fälle unterscheiden einerseits die Lehreliste,
da brauchen wir diese Sonderbehandlung und wir zahlen natürlich einen Preis dafür
für das ganze Wir dürfen jetzt keine Listenelemente mehr haben,
die noch uninstanzierte Variablen sind.
Also wir können auf diese, in diese Warteschlange keine Variablen hineinschreiben.
Denn die würden automatisch als Listenende-Indikator verstanden werden.
Und das ist also eine Einschränkung,
aber typischerweise will man in einer Warteschlange auch keine Variablen verwalten,
sondern man will irgendwelche Suchraumelemente da reinschreiben.
Und das werden Therme sein, die möglicherweise noch Variablen enthalten,
aber selber keine Variable sind
So, dann fangen wir doch mal an.
Als erstes brauchen wir das Einfügen in die Warteliste.
Wir müssen unterscheiden, erster Fall, die Liste ist noch leer.
Das heißt, sowohl das erste Element Y ist eine Variable,
eine uninstanzierte Variable als auch die Restliste.
Es ist eine uninstanzierte Variable. Und wenn das der Fall ist,
dann instanziere dieses Element mit E, dann füge also das
gewünschte Element an die erste Position in der Liste ein.
Im anderen Fall, wir haben hier ein nicht valiables Element und
wir haben hier ein vaiables Element und wir wollen ein neues Element hinzufügen.
Dann müssen wir eben hier, dann sind wir am Listenende
angekommen, denn wir haben hier eine Valiable vorliegen.
Und dann müssen wir diese vaiable instanzieren,
indem wir ihr eine neue Kunstzelle vorwerfen, bestehen,
aus dem einzufügenden Element und einer noch wieder offenen Restliste.
Das ist der Fall, einfügen am Ende der Warteschlange.
Und der andere Fall ist eben der rekusive Aufruf,
jetzt muss ich die offene Restliste suchen. Das heißt,
ich gehe so lange, bis die,
solange die Restliste keine Variable ist,
Solange rufe ich das Ganze rekrusiv auf,
bis ich irgendwann mal die Restliste in der Hand habe,
eine vaiable Restliste in der Hand habe und die zweite Klausel hier anschlägt.
Also das dritte, der dritte Fall ist nur,
baue die Liste rekrusiv ab, bis du am Ende angekommen bist.
Also hier der Test, ich habe eine Lehre,
solche Warteschlange, so eine merkwürdige, ich möchte ein A einfügen,
da möchte ich ein B einfügen,
da möchte ich ein C einfügen und was herauskommt,
ist diese Warteschlange.
Sie enthält A, B und C und eine noch offene Restliste,
in die ich noch weitere Dinge hinzufügen kann
Ich kann den umgekehrten Fall aus einer Warteliste etwas herausholen.
Wenn denn die Warteliste, aus der ich das herausholen möchte,
zwei Variablen enthält, zwei Nichtinstanzen, ihre Variablen enthält,
dann habe ich eine leere Warteschlange vorliegen und ich kann aus ihr nichts herausholen.
Das heißt, in diesem Augenblick scheitert ja, soll der Aufruf scheitern.
Aus einer leeren Warteliste kann ich kein Element entfernen.
Im zweiten Fall habe ich noch eine Einerliste vorliegen und
eine Einerliste ist dadurch gekennzeichnet,
dass ich hier ein Element habe und hier
eine nicht instanzierte Restliste habe
Und dann ist das offensichtlich das letzte Element der Warteschlange.
Das kann ich als Ergebnis übergeben hier und was herauskommt,
ist jetzt eine leere Warteschlange,
die hier eine uninstanzierte Variable und hier eine uninstanzierte Variable hat.
Ja. Dies werden würde sich, dieses Möglichkeit falsch behalten,
wenn man die erste Klausel komplett weglassen würde.
Sie tut ja nicht weiter als den Situationen zu scheitern,
die von den unteren beiden Klauseln ohnehin nicht abgedeckt werden.
Nee, nee, nee, doch,
das ist genau dieser Fall hier
Wenn ich hier nicht teste, dass beide Variablen sind,
kann ich die Lehre Warteschlange nicht mehr feststellen.
Aber dann würde der Aufruf von Faild auch dazu führen,
dass wir der nächste Klauser vorgefallen wird.
Ach so, ja, okay. Ja, ja,
ja, ja, richtig. Weil ich denke,
genau das hier hier teste. Hier wird gefordert,
dass das installiert sein muss, das muss uninstanziert sein.
Ja, sieht so aus, das ist ein Spezialfall davon.
Ja, sorry, habe ich gar nicht gesehen.
Das ist tatsächlich ein Spezialfall von dem hier,
weil ich nämlich diesen, In diesem Falle würden die beiden Bajablen sein,
wenn ich also diese Klausel nicht hätte, dann würde dieser Test hier scheitern.
Und dann kommt das gleiche Fehl raus, was ich hier erzwinge.
Das ist richtig. Ja, hey, Bingo,
das ist, die Programme sind schon ziemlich alt.
Bisher ist noch keiner auf die Idee gekommen, das mal zu hinterfragen.
Finde ich gut. Das ist super. Ja,
und das dritte ist jetzt der sonstige Fall,
ich habe jetzt eine Warteschlange,
die außer dem ersten Element noch weitere Elemente enthält,
das Tee hier hinten, die Restliste für Tale,
das ist keine, das ist keine Variable, keine unnützenzierte Variable.
Das heißt, hier gibt es noch Elemente in der Restliste,
deshalb kann ich es unbesehen als eine noch teilweise gefüllte Warteschlange zurückgeben.
So, und dann testen wir das auf. Wir rufen das
wieder mit einer Warteschlange auf. Wir wollen das erste Element haben,
wir kriegen das B und wir kriegen als restliche Warteschlange die,
die noch das C enthält.
Und im letzten Fall Haben wir nur noch ein Element drin
und dann kriegen wir eine leere Warteschlange als Ergebnis heraus.
Und natürlich dieses Element, was wir haben,
wenn wir hier eine leere Warteschlange übergeben und wir wollen das Element haben,
dann scheitert das Ganze.
Der Nachteil, diese Implementierung ist, der rekursive Aufruf beim Einfügen,
dass ich hier immer noch mal dieses Encuo rekursiv aufrufen muss,
um das Listenende zu finden
Das muss ich ja nicht. Wenn ich das Listen nenne,
bereits kenne. Und das ist genau der entscheidende Schritt,
den wir brauchen für die Differenzlisten. Bei den Differenzlisten merken
wir uns nicht nur, wo der Listen Anfang ist,
sondern wir merken uns auch, wo das Listenende ist.
Also die Stelle, wo ich noch etwas hinzufügen kann.
Tatsächlich wird die Bedeutung der Differenzlisten noch ein Tick allgemeiner sein,
als das, was ich eben beschrieben habe.
Das ist nur ein Spezialfall davon. Aber die Idee ist
schon erstmal da. Ich merke mir eben nicht nur die Stelle,
wo ich wegnehmen kann, vorne,
sondern ich merke mir auch die Stelle, wo ich hinzufügen kann,
da hinten.
Und dann muss ich nicht mehr suchen
Dann fällt dieser rekrusive Durchlauf durch die Liste weg.
Und das ist genau das, was wir haben wollen.
Wir wollen eine Datenstruktur haben, wo wir hinten etwas hinzufügen können,
ohne dass wir die Rekursion dafür brauchen.
Das war ja der ursprüngliche Einstieg beim Stack,
können wir immer am Anfang einfügen und wegstreichen,
weil wir den letzten Anfang in der Hand haben.
Und genau das wollen wir hier auch haben.
Den Preis sehen wir dafür bezahlen müssen,
ist, dass wir uns eben zwei Sachen merken müssen.
Wo wird eingefügt und wo wird weggestrichen
Gut, dafür brauchen wir den Begriff der Lystendifferenz,
Listendifferenz heißt, ach so, das ist wegen der Bequemlichkeit,
gibt es einen Operator, der es typischerweise vordefiniert,
das muss man nicht selber machen,
aber wenn man ihn haben will, kann man ihn so definieren.
Es ist ein symmetrischer Operator und erlaubt keine rekursive Einbettung.
Also in der Hierarchie weiß ich jetzt gar nicht,
wo er da eingeordnet ist, das kann man sich aber angucken.
Mit diesem Operator beschreibe ich jetzt die Differenz zweier Listen.
Der Operator selber hat keinerlei Semantik
Es ist einfach nur eine syntaktische Auszeichnung für Differenzlisten.
Ich brauche ihn auch nicht. Ich kann eine solche Differenzliste
immer auf zwei Argumenten eines Pädikats verwalten,
indem ich sage, hier ist der erste,
der erste Teil hier ist der zweite Teil.
Also ich brauche ihn streng genommen nicht,
aber er hat sich eingebürgert als ganz nützlich,
um deutlich zu machen, hier, ich arbeite mit Differenzlisten.
Ansonsten, es ist, ansonsten ist die Bedeutung dessen,
was man da macht, reichhaltig nebulös.
Und wir werden sowieso sehen,
es wird jetzt
zunehmend spannend
Also ein Operator, der ausschließlich für den menschlichen Gebrauch bestimmt ist.
Die Maschine braucht ihn nicht. Aber um das Lesen
der Programme etwas einfacher zu machen, wird er eingeführt.
Es wird also keinerlei Prädikat geben, was irgendwie mal dem Ding eine Semantik gibt.
Wir brauchen einfach nur einen Syntagskonstrukt und das führen wir hier ein.
Und wir wollen das verstehen,
dass wir das als Listenanfang bezeichnen und das hier als das,
was wir von dieser Liste wieder abziehen müssen.
Also hier haben wir eine Liste,
die enthält drei Elemente und wir ziehen kein Element davon ab
Hier haben wir eine Liste A, B, C,
D und wir ziehen eine Liste D davon wieder ab.
Übrig bleibt also, wenn wir dieses D hier abziehen,
hier hinten, bleibt die Liste A, B,
C. Also das ist nicht ein D,
was irgendwo abgezogen wird, sondern es sagt praktisch,
was ist die Restliste, die übrig bleibt,
nachdem ich diese Liste davon subtrahiere.
Und die Rest lässt, oder der Listenanfang hier ist eben die Liste A,
B, C, dann ziehe ich diese Liste hier ab.
Das soll das ausdrücken. Oder eben hier,
ich habe die Liste A, B,
C.de, ich ziehe die Liste D,
E davon wieder ab und erhalte wieder die letzte A,
B,
Und so weiter und so fort. Und das kann ich
generalisieren zu einem solchen Ausdruck, der sagt,
ich habe eine Liste mit drei Elementen und einem offenen Ende.
Und was immer hier instanziert wird, ich ziehe es sofort
wieder ab davon. Was immer hier die Restliste ist,
sie wird wieder abgezogen davon, sodass das hier die Bedeutung hat,
ich bin die Liste mit drei Elementen, die heißen ABC.
Nur, dass ich sie jetzt repräsentiert habe, als Liste mit offenem Ende.
Und ich kenne jetzt hier hinten die Stelle,
wo ich hineinbauen kann. Und ich kenne natürliche Formen,
die Stelle, wo ich am Anfang
etwas hinzufügen kann
Die Idee, die dem Ganzen zugrunde liegt. So,
und jetzt können wir das benutzen, um unsere bratischen Anverwaltung wieder zu modifizieren.
Also, eine leere Differenzliste ist eine leere Liste, von der ich eine Lehrenliste abziehe.
Und wir fügen etwas in die leere Differenzliste ein,
und zwar so, indem wir sagen, okay,
dann bilden wir eben eine Liste,
die dieses gewünschte Element enthält, danach möglicherweise noch weitere Elemente,
aber die ziehen wir sofort wieder ab.
Sodass insgesamt die Bedeutung bleibt, wir haben hier eine einer Liste,
die besteht aus diesem Element und möglicherweise noch irgendetwas danach,
aber darum kümmern wir uns nicht.
Unsere Bedeutung im Kopf dafür ist, wir subtrahieren das sofort wieder.
Und das Gleiche können wir jetzt für das Einfügen sonst machen.
Wir wollen ja am Ende einfügen. Wir müssen jetzt, ja,
das ist jetzt ein bisschen von hinten nach vorn zu denken.
Jetzt wird es langsam spannend. Wie gesagt,
ich habe mir die schönsten Teile für den Schluss aufgehoben.
Wir müssen jetzt denken in den Begriffen, alte Q,
neue Q. Hier hinten haben wir eine neue Cue.
Nach Einfügen dieser neuen
Haben wir also hier eine Liste, die dieses Element enthalten
soll und wir ziehen davon wieder etwas ab.
Und dieses hinzuzufügende Element muss in Left als Letztes enthalten sein.
Das heißt, wenn wir die alte Differenzliste haben wollen,
müssen wir aus dem Left-Teil nicht nur Wright abziehen,
sondern das gerade eben eingefügte Element eh auch.
Also das hier ist eine um eins kürzere Liste als diese.
Und zwar ist genau das letzte Element hier nicht drin,
was hier drin ist
Also, wir kreieren hier eine Differenzliste,
die hier in Left ein letztes Element enthält,
was in der alten Differenzliste nicht enthalten sein durfte.
Das ist jetzt ein bisschen von hinten nach vorne argumentiert,
aber ich kenne keine bessere Argumentation dafür.
Man muss sich einfach darauf einlassen, dass wir jetzt eine deklarative,
wir reden jetzt nur noch über deklarative Semantik von dem Ganzen.
Wir haben keine prozedurale Semantik mehr dahinter.
Es ist einfach nur noch statische Bedeutung von Co-Referenzgleichungen.
Und das ist die Lösung für dieses ganze Problem,
was jetzt kommt, dass wir die prozedurale Semantik fast vollständig rausoperieren.
Wir müssen uns zum Schluss fragen, wo ist sie
eigentlich noch
Denn im Endeffekt haben wir einen Rechner dastehen,
der hat einen Prozessor da drin.
Irgendwo muss die prozedurale Semantik sein. Aber wo ist sie?
Wir gucken uns erst mal an, ob es wirklich funktioniert.
Also wir haben eine leere DVA, ich führe jetzt einfach mal Testelemente ein.
Eine leere Warteschlange, eine einelementige und eine zweielementige.
Und mit denen operieren wir jetzt ein bisschen. Also wir nehmen die Lehrer,
wir wollen in diese Lehre differenzliste Kuh jetzt ein neues Element
hinzufügen und wollen dann die neue Liste finden.
Netterweise kriegen wir für Kuh Das ist ja die alte Differenzliste,
die ändert sich nicht. Die sieht immer noch so aus,
wie vorher auch. Was wir aber kriegen, ist die neue Differenzliste,
die sagt, ich enthalte A und möglicherweise noch weiteres,
aber das ziehe ich sofort wieder ab.
Das ist genau das gewünschte Ergebnis. Jetzt habe ich hier eine,
eine Warteschlange, in die ich ein neues Element B hinzufügen will.
Und das wird jetzt, wie gesagt,
wir gucken uns mal erst den den rechten Teil hier ab.
Ich muss also hier dieses, eine Konzelle hineinunifizieren,
wo B das erste Element ist.
Das ist genau das, was unsere Definition von uns fordert.
Hier in dem rechten Teil dieses Arguments kommt eine Konzelle hinein,
die sagt, ich bin das Neue,
hinzuzufügende Element und möglicherweise gibt es noch weitere Dinge,
die ich abziehen muss.
Und das sehen wir jetzt hier. Hier taucht das B jetzt plötzlich auf. Moment,
sorry, hier taucht das B jetzt Achso, nein,
ja, hier sehen wir jetzt den Blick hinter die Kulissen.
Wir gucken uns erstmal den Blick vor den Kulissen an.
Der Blick vor den Kulissen ist, wir kriegen hier
eine Warteschlange heraus, die das alte Element A enthält.
Das, was vorher schon drin war, danach kommt ein B hinein.
Möglicherweise gefolgt von anderen Dingen, die wir aber sofort wieder abziehen.
Und hier ist der Blick hinter den Kulissen. Das,
was wirklich passiert ist, die alte Warteschlange wird modifiziert.
Und zwar wird sie so modifiziert, dass wir hier jetzt
diese Konzelle hineingesetzt haben, mit dem B davor.
Und um die Äquivalenz wiederherzustellen, führt das auch dazu,
dass wir hier Form das ebenfalls hineinunifiziert haben.
Und das können wir tatsächlich in der Definition sehen.
Was immer die Ergebnisliste ist, das ist ja eh gefolgt von,
also das gewünschte Element, gefolgt von irgendetwas,
muss auch hier links auftauchen.
Und da natürlich in der neuen Warteschlange soll ja das neue Element drinstehen.
Also muss es auch hier in diesem linken Teil drinstehen.
Denn das ist per Core-Referenz
aneinander gebunden
Und deshalb muss ich also dieses Teil hier so ergänzt werden,
dass insgesamt wieder die alte Differenzliste herauskommt.
Also, das ist ziemlich trickreich. Wir modifizieren die alte Differenzliste,
indem wir hier in die alte Differenzliste neue Elemente hineinunifizieren,
eine neue Kunstzelle, sowohl ihr als auch da.
Und wir sorgen eben nur dafür,
dass es sowohl hier als auch da passiert,
sodass sich an der Bedeutung dieser Differenzliste nichts ändert.
Es ist eben jetzt die Liste, die A enthält,
B und gefolgt von irgendetwas,
aber B gefolgt von irgendetwas wird sofort wieder subtrahiert
Also in unserem Kopf entspricht dies der alten Differenzliste,
de facto in der Implementierung ist es nicht mehr die alte.
Sie hat sich geändert,
sie hat sich von dem Ausdruck hier zu diesem Ausdruck gewandelt,
weil hier für dieses X etwas hineinunifiziert worden ist.
Nämlich genau dieses gepunktete Paar, wie gefolgt von X2.
Das ist hier hineinunifiziert worden. Und wenn es wegen
der Co-Referenz muss es auch hier hineinunifiziert werden.
Und auf die Art und Weise erreichen wir,
dass eben die neue, die neue Differenzliste a,
gefolgt von diesem gepunkteten Paar enthält
Genau so wie das hier. Die beiden sind per Co-Referenz
ja miteinander verbunden. Die müssen zwangsweise gleich sein. Und
auf die Art und Weise wird die alte Differenzliste sowohl hier als
auch da wegen dieser Co-Referenz, um das Element B erweitert.
In unserem Kopf ist es immer noch die alte Differenzliste.
Im Rechner sieht sie etwas anders aus. Und
das Spiel können wir jetzt fortsetzen. Wenn wir jetzt also
eine Zweierdifferenzliste nehmen, wir wollen ein neues Element hinzufügen,
wird auch hier wieder eingepunktetes Paar reingesetzt, bestehend aus C und X3.
Und das muss wegen der Co-Referenz hier auch hier,
also wegen dieser Co-Referenz,
auch hier hinein unifiziert werden
Und wegen der Co-Referenz im Programm, zwischen dem linken Teil
von der alten und dem linken Teil der neuen Liste,
muss das, was hier steht, gleich dem sein,
was hier steht. Und das, was hier steht,
ist eben wieder per Chro-Referenz dann sofort die gleiche Liste,
die ich wieder abziehen muss. So, also,
das ist jetzt etwas, was ich prozedural,
das merken Sie schon, wie ich stottere und Ringe um Formulierungen,
was ich prozedural kaum noch darstellen lässt.
Hier muss ich mich jetzt wirklich darauf verlassen,
dass die deklarative Semantik des Programms
die Sache trägt
Nämlich, und die deklarative Semantik des Programms ist nichts anderes,
als dass sich eine Menge von Co-Referenzforderungen aufstelle.
Nämlich der linke Teil hier muss gleich dem linken Teil hier sein.
Und diese Restliste hier muss gleich diesem rechten Teil hier sein.
Diese beiden Forderungen, die ich aufstelle, zusammen mit der Co-Referenzforderung in den Einbegabedaten.
Diese drei Co-Referenzforderungen, die machen das Ergebnis aus.
Und wir sehen hier eben, das ist ein trickreiches Zusammenspiel aus Dingen,
die im Programm drinstehen und Dingen, die in unseren Daten drin stehen.
Die Daten haben plötzlich eine prozedurale Bedeutung bekommen. Die Daten erzwingen mir,
ganz bestimmte Ergebnisse, weil ich hier bereits drin fordern kann,
das, was hier steht, muss mit dem unifizieren,
was da hinten steht.
Und diese drei Forderungen zusammen machen dann das Ergebnis aus.
Was wir jetzt haben, ist also,
wir können die dramatisch lange nach rechts erweitern,
wir müssen nicht mehr kopieren und wir müssen nicht mehr suchen,
wo das rechte Listenende ist.
Denn wir wissen bereits, wo ist Porsche.
Wir wissen vorher bereits, wo wir es finden können,
weil wir den Pointer darauf in der Hand haben.
Einen Problemfall gibt es, in der Implementierung,
die wir jetzt haben,
der ist mir auch irgendwann mal erst
viel,
viel später aufgefallen
Was ist das für eine Warteschlange? Ja. Das ist
die leere Warteschlange. Und ich fordere jetzt von ihr,
dass ich ein Element aus dieser leeren Warteschlange entferne.
Kein Problem, macht er. Dann sagt er eben,
okay, die alte Warteschlange ist die, die dieses Element mal erhalten hatte.
Sonst kann ich es ja nicht entfernen. Und ich muss natürlich sicherstellen,
dass das nach wie vor eine leere Warteschlange ist. Also alles,
was hier drin ist, muss auch hier drin sein,
damit die Differenz wieder eine leere Warteschlange ergibt.
Dann ist dieses Element, was ich entferne, diese Warte,
wegen dieser Formulierung hier, jetzt kommen die Ko-Referenzforderungen wieder,
muss also auch dieses alte X hier das gepunktete Paar sein,
aus dem zu entfernenden Element und einem möglichen Rest.
Und die neue Warteschlange, die jetzt herauskommt, ist,
die hier hinten, von der ich jetzt eine Warteschlange abziehe,
die aber um eins länger, äh Quatsch,
eine Liste abziehe, die aber um eins länger ist,
als das, was hier vor uns steht.
Jetzt ziehe ich von einer, sagen wir mal,
Zweierliste eine Dreierliste ab
Und vor deiner Vierer-Liste ziehe ich eine Fünferliste ab und so weiter und so fort.
Was wir hier haben, ist eine Liste negativer Länge.
Und da wird es richtig spannend. Wir können jetzt über
fiktive Datenstrukturen reden, die es eigentlich gar nicht gibt.
Aber wir können Ausdrücke hinschreiben, die sie beschreiben. Wir können sagen,
ich bin eine Liste mit minus eins Elementen,
denn ich bin entstanden aus einer Warteschlange,
die nichts enthält und die aus den Nichtselementen habe ich eins herausgenommen,
nämlich genau eh.
Ja, und damit entsteht die neue Warteschlange,
nachdem ich das eh rausgenommen habe,
ist eine Warteschlange,
die minus eins Elemente enthält
Ich kann das sogar hinterher wieder reparieren.
Ich kann in diese mega Warteschlange negativer Länge wieder Elemente hinzufügen.
Bist du plötzlich wieder positiv, viele Elemente teilt.
Also ich kann auch mit Warteschlangen negativer Länge arbeiten auf diese Art und Weise.
Es geht. Natürlich für praktische Zwecke möchte man das
nicht haben. Man möchte für ein praktisches Programmierproblem,
also ich benutze das beispielsweise für eine Breitensuche,
dann will ich wissen, wann die Suche beendet ist.
Das ist ja ein Terminierungsproblem, was hier auftaucht.
Ich habe das letzte, das nicht vorhandene Element heraus entfernt
und kriege dann eine Warteschlange der Länge minus eins.
Da kann ich natürlich auch eine der längeren minus zwei
bilden und so weiter und so fort.
Ich kriege ein ernsthaftes Terminierungsproblem in das Ganze rein.
Das will ich normalerweise nicht. Aber im Prinzip geht es.
Und das sind jetzt so die Stellen in der Logikprogrammierung,
wo sie anfangen, langsam die Nackenhaare aufzukräuseln,
wo man sich fragt, wovon reden wir jetzt eigentlich noch?
Ist das etwas rein Fiktives oder ist das,
hat das noch eine reale Semantik in der Welt?
Also, wir reden jetzt über Listen mit negativer Länge.
Eigentlich ja nur eine Konsequenz. Ein konsequentes Vorgehen.
Wir hatten ja gesehen, dass wir Listen als Äquivalent für
natürliche Zahlen
verwenden können
Indem wir eben den Wert der natürlichen Zahl immer die Länge der Liste nehmen.
Nichts anderes sind ja unsere Pianu-Zahlen gewesen. Wir bauen eingebettete,
rekursive Strukturen auf und je mehr Einbettungsebenen wir haben,
umso größer ist die Äquivalente natürliche Zahl dafür.
Das können wir das eine, das andere jeweils eins zu eins umrechnen.
Das, was wir hier jetzt machen, ist die Erfindung der Inditure-Zahlen.
Wir erweitern jetzt die natürlichen Zahlen zu den Inditure-Zahlen,
indem wir plötzlich negative Zahlen auch noch zulassen.
Gut, also man kann es natürlich reparieren,
wenn man es möchte und die Reparatur ist hier,
ich muss das Entfernen blockieren, falls die Warteschlange leer ist.
Das heißt also, Ja, wenn ich das jetzt noch hinkriege,
die Bedingung ist ein bisschen trickreich. Ja, ich muss sicherstellen,
dass es diese Bedingung hier, ich muss sicherstellen,
dass hier der linke und der rechte Teil hier nicht identisch sein dürfen.
Und dann es eben genau dies nicht mehr möglich,
dieser Aufruf hier, wenn hier zwei Ausdrücke stehen,
die miteinander odifizieren oder die schon vor vornherein identisch sind,
dann wird das Ganze blockiert
Also ein Entfernen aus einer solchen Warteschlange soll verhindert werden und
das macht genau dieser Vergleichsausdruck, der hier steht.
Und das kann man dann sich eben angucken. Also wenn man jetzt verlangt,
dass aus dieser Warteschlange noch etwas entfernt ist,
dann kommt eben tatsächlich ein False heraus und die anderen Sachen sollten tatsächlich noch funktionieren.
Also aus der einer Warteschlange kriege ich eben das erste Element
heraus und bekomme eine solche Warteschlange zurück.
Aus der Zweier, das stimmt auch,
dann ist noch ein Element drin
Und der Witz an der Sache ist eben, diese Ausdrücke können jetzt
beliebig komplex sein. Das müssen nicht leere Listen sein,
das können eben bei Jablen sein, wie hier oben,
im ersten Fall, das können leere Listen sein,
aber es können auch partiell instanzierte Listen sein.
Also wenn dieses Ding ist ja in unserem Kopf auch die leere Warteschlange,
auch das blockt eben dieser Test hier ab.
Ja, blockt der Test ab, der sicherstellt,
dass in so einem Fall nichts mehr aus der Warteschlange entfernt werden kann.
Denn in diesem Fall reicht es ja auch nicht aus,
zu gucken, Hier stehen ja Elemente drin.
Rein physisch. Hier ist ein Element drin,
aber in unserer Differenzlisten-Interpretation ist das die leere Warteschlange.
Das ist ein bisschen trickreich. Also Botschaft ist nur,
man kann es reparieren, wenn man es möchte,
aber man kann natürlich auch spielen auf die Art und Weise und fragen,
was passiert dann eigentlich?
So, und damit haben wir jetzt ein Werkzeug in der
Hand, was relativ mächtig ist.
Wir wollen das jetzt und gucken uns an,
wofür man das alles verwenden kann.
Und da gibt es tatsächlich eine ganze Reihe von Anwendungsfällen,
die in Frage kommen.
Das, was sie schon am Anfang angekündigt hatte,
war ja
das Appent
Die Definition stand auch schon auf der Folie. Das ist
jetzt ein Appent für Differenzlisten. Wir haben eine Differenzliste,
wir haben eine zweite Differenzliste und wir haben die Ergebnisdifferenzliste.
Und was dieser Ausdruck jetzt sagt, ist, ich habe eine Differenzliste,
die fängt hier an bei X und hört bei Y auf.
Wir haben die Interpretation der Warteschlange. Das erste Ding vor
dem Backslash war immer, wo füge ich ein?
Oh, wo streiche ich, wo hole ich raus aus der Warteschlange?
Und das zweite war, wo füge ich ein?
Also das erste ist ein Pointer auf den Listen Anfang.
Das zweite ist ein Pointer auf das Listenende
Und genau so ist das hier. Wir haben eine Liste,
die fängt bei X an und hört bei Y auf.
Dann habe ich eine zweite Liste,
die fängt bei Y an und hört bei Z auf.
Und dann ist das Verketten dieser beiden Listen eben genau das,
was bei X anfängt und bei Z aufhört.
Oder andersherum, ich zwinge durch diese Co-Referenz,
dass das Listenende der ersten Liste gleich dem Listen Anfang der zweiten Liste ist.
Und ich nehme, dass Listen den Anfang der ersten Liste
als den Anfang der Gesamtliste und ich nehme das Ende der
zweiten Liste als das Ende der Gesamtliste
Dass es hier also nochmal versucht darzustellen,
wir haben eine erste Liste, die enthält A,
B, C, gefolgt von irgendetwas Weiterem,
was sich aber sofort wieder abziehe.
Also diese Differenzliste sagt mir, ich bin eine Liste A,
B, C und danach kann von mir aus noch irgendetwas kommen,
aber da spielt keine Rolle.
Das interessiert uns nicht. Das Zweite sagt,
ich bin eine Differenzliste, die enthält D-E-F und möglicherweise noch weiteres.
Und das Z ist dann die Verkettung von den beiden,
dass durch diese Co-Referenz, wo immer diese Liste hier aufhört,
muss diese Liste anfangen, soweit ist das das Ergebnis ist.
Auch hier können danach noch weiteres folgen,
das interessiert uns aber nicht
Und das X ist eben der Pointer auf dieses A hier.
Und das Z ist der Pointer auf dieses F,
sodass ich weiß, die Ergebnisliste fängt hier an und hört da auf.
Möglicherweise gefolgt von weiteren Elementen. Und wenn ich das so mache,
und wir können uns davon überzeugen, es funktioniert wirklich,
dann ist das Appent eben nur noch emphaft.
Es ist keine rekursive Prozedur mehr. In der funktionalen Programmierung,
da es eben auch eine rekursive Prozedur, die nicht mal endrekursiv ist.
Wir haben es in die Logikprogrammierung übernommen,
da wurde es endrekursiv dank der Richtungsunabhängigkeit der Unifikation.
Und jetzt haben wir auch noch
die Rekursion rausgeschmissen
Ist es Magie oder ist das noch real? Wo ist die Rekursion geblieben?
Also auf den ersten Blick brauchen wir ja keine Rekursion mehr,
weil wir uns das Wissen nennen und den Ball anhängen wollen, speichern.
Es kann natürlich jetzt bei entsprechender Instanz die Iron passieren,
dass die Rekursion in der Unifikation verschwindet.
Denn die Unifikation wird ja auch rekursiv vorgenommen.
Rekursivo-Unifikation. Ja, genau das ist der Trick.
Ich muss natürlich die Listen noch miteinander unifizieren.
Denn diese Chorreferenzforderung hier drin heißen ja nichts anderes,
als dass ich Das, was hier steht,
mit dem udifizieren muss, was da hinten steht.
Und das sind Listen. Und Listen sind rekursive Datenstrukturen.
Und dann heißt die Unifikation auch immer, udifiziere den Kopf,
unifiziere die Restliste. Das ist die Bedeutung,
oder die Anwendung der Unifikation auf die Unifikation von Listen.
Also die Rekursion verschwindet nicht, sie ist nur im Unifikationsopperator verschwunden.
Da steckt sie jetzt. Ich sehe hier jetzt ein Problem,
wenn man zweimal versucht, an dieselbe Differenzliste unterschiedliche Listen mit der Pen-Hitten anzuhängen.
Denn beim zweiten Mal ist die Restliste Y dann schon instanziert.
Und wenn man jetzt ein anderes Y ohne Z da dran hängen will,
dass damit nicht unifizieren wird, er pennt einfach viel stark.
Ja, ja. Aber da hilft uns sehr,
dass eben der Gültigkeitsbereich von Variablen sehr beschränkt ist.
Also es gibt nur, man muss sich schon Mühe geben,
den Fall zu erzeugen. Man kann ihn natürlich erzeugen, ja.
Das geht. Aber es muss innerhalb einer Klausel passieren.
Außerhalb der Klausel ist es wieder eine neue Liste und
dann neues Glück, da geht es von vorn los.
Gut, also konstanter Zeitbedarf ist natürlich nicht,
nur so lange wie wir eine Unifikationsoperation als konstante Operation ansehen,
dann sieht es so aus.
Also wenn wir beispielsweise uns die Die Ressourcenanforderungen vom System ausgeben lassen,
dann werden wir eben bei Logical Influences per Second eine konstante Zahl bekommen.
Weil eben, es ist eine Operation, die durchgeführt wird.
Aber die Operation hat natürlich eine inhärente Komplexität. Der Zeitbedarf
in Realzeit gemessen ist natürlich nicht konstant. Aber was wesentlich ist,
und das ist, wir brauchen kein Kopieren,
also es entsteht kein zusätzlicher Freispeicherbedarf dabei, aber mit dem Nebeneffekt,
dass eben die ursprünglichen Listen verändert werden und nicht mehr in der alten Form zur Verfügung stehen,
Und es gibt noch einen weiteren Anwendung,
da kommen wir aber dann bei den Grammatiken drauf.
Ich kann auf diese Art und Weise sehr frühzeitig variablen instanzieren.
In dem Augenblick, wo ich hier diese Anfangsliste aus
irgendeinem Grunde erkannt habe, weiß ich den Wert für Y.
Und dann kenne ich den Anfang dieser Liste. Und das,
oder ich weiß, wo ich dann hier weitermachen muss,
das ist für, oder sagen wir mal, der Fall ist umgekehrt.
Ich will also beispielsweise bei der syntaktischen Analyse diese Kette hier zerlegen.
Und ich habe hier die ersten Elemente als eine bestimmte
Konstruktion identifiziert
Danach muss es gefolgt werden, von einer weiteren Konstruktion.
Also stellen Sie sich Programmiersprachenausdrücke an der Stelle vor.
Sie haben irgendwelche If-Fan-Els-Klauseln. Sie haben das hier erkannt als ein If-Fan
. Und da muss jetzt irgendwann ein Els dahinter kommen.
Dann wissen Sie schon, an welcher Stelle das Els stehen muss,
wo Sie hingucken müssen. Also das ist ein weiterer Vorteil,
auf die Art und Weise kann ich frühzeitig Vajablen instanzieren.
Und dadurch wird das Arbeiten mit Grammatiken effizient. Aber dazu kommen wir dann nächste Woche. Gut,
hier sind also Testaufrufe der Art, wie ich habe eine einer Liste,
die enthält nur A und eine Zweierliste,
die enthält A und B und ich soll sie eben Ich soll sie so verknüpfen,
dass L2 die Ergebnisliste ist und LX, ja Quatsch,
das L2, die Gesamtliste ist und LX ist die gewünschte Ergebnisliste,
die sagt, was muss ich zur Liste A hinzufügen,
dass die Liste AB entsteht?
Und das Ergebnis ist eben, dass wir die Liste,
die B enthält, dort dazu hinzufügen müssen.
Und was wir auch hier wieder sehen, die Liste 2 hat
sich nicht verändert, aber die Liste 1 hat sich verändert.
Das ist der gleiche Trick, den wir eben bei der Warteschlange gesehen haben,
während ihr hier eben nur die Liste hattet,
die A enthält und irgendetwas weiteres, was wieder abgezogen wird.
Es ist jetzt eben die Liste, die als Prefix AB
enthält, aber B, irgendetwas wird wieder abgezogen.
Also in unserer Interpretation, es ist immer noch die gleiche Differenzliste.
Physisch im Rechner ist es eine instanzierte Variante davon. Was haben wir hier? Eine einer Liste,
eine Zweierliste und wir wollen die auf klassische Weise miteinander verketten und nicht überraschend,
kriegen wir dann eben als Ergebnis die Liste, A,
A und B enthält heraus, aber wir sehen eben,
dass die Liste 1 entsprechend angereichert wurde durch Informationen,
in denen eben diese Teilliste hier hineinunifiziert worden ist.
Und weil sie hier hineinunifiziert worden ist, da nämlich,
muss sie auch da hineinunifiziert werden und wird sofort wieder abgezogen.
Das ist der Trick hier dabei. Ich unifiziere es hier hinein,
ich unifiziere es da hinein und indem ich es hier hineinunifiziere,
enthalte ich diesen ersten Teil hier und den zweiten Teil
übernehme ich dann eben aus diesem Pointer dahinten.
Gut, hier Ach so, ja, das ist ein ganz hübscher Fall.
Das ist ein zerlegen. Wir zerlegen jetzt die Liste,
die zwei Elemente hat, in ihre Teillisten.
LX und LY. L2 ist die vorgegebene Liste. Und
das ist jetzt ganz witzig. Jetzt kriegen wir also diese Ausdrücke heraus.
Und das muss man sich auf der Zunge zergehen lassen.
Was jetzt hier eigentlich passiert. Also für X kriegen wir heraus,
es ist eine Liste AB, gefolgt von irgendetwas.
Und wir ziehen etwas völlig anderes davon ab.
Aber es ist nicht beliebig anders, was wir abziehen.
Es ist genau das Kompliment dessen,
was wir hier haben
Hier ist es X3, von dem wir X2 abziehen.
Hier ist es X2, dann nehmen wir jetzt 3 ab
7. Also, was passiert hier? Wenn wir früher,
er pennt auf die Art und Weise aufgerufen haben, also mit normalen Listen,
wie viele Ergebnisse haben wir für den Aufruf gekriegt, er pennt,
something bit, something, sodass die Liste AB entsteht.
Entsprechend der Länge der Liste. Ja, wir haben ja eine Zweierliste, ja.
Also, in diesem Falle, wie ist es hier?
13, 14. Genau, die kommt immer mit dazu.
Also, der klassische Aufruf, Appent, XY zu AB.
Das ist ja das, was dem hier entsprechen würde.
Das würde eben als erstes X gleich, na,
ob als erstes, doch ja X gleich leere Liste,
Y gleich Liste AB.
Als zweites X gleich Liste, die A enthält,
Y gleich Liste, die B enthält.
Und als letztes X Gleichliste
die AB enthält
Und Y, die leere Liste.
Das wären die drei Ergebnisse, die wir kriegen.
Jetzt kriegen wir ein Ergebnis mit ziemlich unverständlichen Co-Referenzforderungen drin.
Wie müssen wir dieses Ergebnis interpretieren? Ja,
es wird sozusagen gesagt, man kriegt Teil ist,
oder Listenzerlegung, mit dem man die Liste an einer Stelle
durchschneidet und wo sie durchgeschnitten wird und von X3 eingegeben.
Genau, und das haben wir in der Hand.
Das können wir frei variieren. Also es muss natürlich wieder sinnvolle Differenzlisten rauskommen,
aber was kann ich von dieser Liste hier sinnvollerweise subtrahieren,
sodass sinnvolle Differenzlisten rauskommen.
Es ist entweder X2, dann ist das X die Variante AB.
Oder es ist B gefolgt von X2,
dann ist das LX die Liste,
die A enthält
Oder es ist die Liste A, B,
gefolgt von X2, dann ist das X die leere Liste.
Und nehmen wir jetzt den letzten Fall, X2 ist die leere Liste,
dann kriegen wir, und wir haben die Liste A,
B, gefolgt von der irgendetwas an X3 gebunden,
damit hier die leere Liste entsteht.
Dann haben wir hier A, B gefolgt von irgendetwas,
minus der leere Liste. Das heißt, in dem Falle,
wo wir X die leere Liste zuweisen, ist Y zwangsweise,
die lässt du die A, B enthält.
Und wenn wir hier die Liste B, gefolgt von X2 abziehen,
Dann haben wir hier die Liste, B gefolgt von X2,
von der wir jetzt ganz ruhig bleiben, wie gefolgt von X2 abziehen,
dann ist das B gefolgt von X2. Natürlich, ja,
und X2 ist die leere Liste.
Also dann ist LY gleich B. Das ist diese Variante.
Und das ist genau dieses komplette Winterverhältnis. Was immer
wir hier abziehen, das muss hier realisiert sein.
Und damit sieht man auch wieder, dass die Differenzlisten,
das haben wir vorhin schon bei der Warteschlange gehabt,
nichts anderes sind als eine Algebra,
die wir über Listen formulieren
Und hier haben wir jetzt eben die Summation von Listen definiert,
mit der Möglichkeit der Umkehroperation oder der Zerlegung in so Mannen.
Das ist der Trick, der hier passiert ist. Okay,
ich glaube, ich habe leicht überzogen, ich bitte um Entschuldigung,
wünsche ein schönes Wochenende und bis nächste Freitag.