So, einen schönen guten Tag. Ich werde ja sofort
immer jede Vorlesung mit einem Cartoon beginnen.
Das ist gedacht zu Ihrer Erheiterung. Es hat aber auch
einen didaktischen Hintergrund. Eines der zentralen Themen in der Veranstaltung
wird Rekursion sein. Und Ihre Aufgabe ist es immer,
sich die Sachen mal anzugucken, zu überlegen,
wo ist die Rekursion, was ist es für eine Rekursion,
welche Eigenschaften hat sie und so weiter und so fort.
Also das sind alles Beiträge zu einem und demselben Thema,
die Sie einfach mal sich zu Gemüte führen können
So, wir hatten uns kurz verschiedene Paranigmen im Vergleich angeguckt.
Hier ist nochmal der Überblick dazu. Wir haben also die
drei großen Berechnungsmodelle, die in der Programmierung eine Rolle spielen können.
Das ist einerseits das emperative Modell, was in SE1, SE2 hauptsächlich gepflegt worden ist.
Das ist zweitens das funktionale Modell und drittens das logikbasierte Modell.
Basierend tun sie alle auf der gleichen Hardware-Architektur und die Hardware-Architektur
ist eine sequenzielle von Neumann-Architektur, teilweise parallelisiert.
Und diese von Neumann-Architektur mit ihrem Grundsatz mit Prozessor und Speicher und Speicher,
sowohl in der zweifachen Verwendungsweise für Daten wie für Programme,
ist etwas, was diese imperative Programmierungssteig nahelegt.
Denn die elementaren Operationen der emperativen Programmierung,
also die Wertezuweisung oder die Update-Operation, nimm den Inhalt der Zelle X,
addiere etwas drauf und mache es zum Neuen Wert der Zelle X.
Das ist in der von Neumann-Maschine verankert. Jedes andere Kalkül,
was ich, oder das andere Berechnungsmodell,
was ich auf dieser gemeinsamen Hardware Architektur aufbauen will,
erfordert, eine mehr oder weniger abstrakte Maschine,
um die Berechnungsoperation etwa funktionale Auswertung oder aber logisches Schließen tatsächlich realisieren zu können.
Das mathematische Grundkonzept,
also nein, das Grundkonzept der imperativen Programmierung ist,
also die Anweisung und die Zuweisung von Werten zu Speicherzellen im Falle der funktionalen Programmierung,
ist es das Auswerten von Funktionen.
Ich habe Funktionen gegeben und der Funktionswert,
den ich berechne,
ist das gewünschte Berechnungsergebnis
Im relationalen Modell ist es eben der Test auf Enthaltensein in einer Relation,
der dahingehend erweitert wird, dass er nicht nur einen bulschen Wert liefert.
Ja, das ist enthalten oder nein, sondern wenn ja, unter welchen Bedingungen.
Also welche Werte für die Vajablen in einer Relation kann ich annehmen,
sodass die Zugehörigkeit zur Relation wahr wird. Ja,
auf der Phase der abstrakten Maschine ist das dann der Prädikatenkalkül,
erster Stufe, das ist der Lamderkalkül, das ist die Touring-Maschine.
Und die Grundoperation, die man dann eben hat,
das Abarbeiten von Anweisungen, Auswerten von Funktionen oder ableiten, logischer Schlüsse.
Und daraus resultiert dann das Verarbeitungsmodell, was in einer konkreten Programmiersprache implementiert ist.
Und da geht es jetzt munter durcheinander, was im Programmierstil betrifft,
kann ich im Imperativen Modell natürlich imperativ arbeiten,
aber in den meisten Fällen auch funktional.
Oder wenn ich das funktionale Modell habe,
dann ist das funktionale Programmieren damit natürlich besonders naheliegend,
aber es ist die Hintertür zum Imperativen Programmieren ist immer offen.
Und im Logikbasierten Modell ist Logikorientiertes Programmieren nahegelegt,
aber wir werden sehen,
wir brauchen ein Teil funktionales Denken und wir sind natürlich auch in der Lage,
emperativ zu programmieren,
wenn wir das wollen,
auch wenn die Ergebnisse dann
weitgehend unverständlich sind
Gut, nun kommen ein paar Werbesprüche zur Logikprogrammierung.
Und dann geht es auch gleich los.
Die Logikprogrammierung zeichnet sich durch ein relativ hohes Abstraktionsniveau aus.
Das ist das, was ihr immer wieder vorgeworfen wird.
Man kann das alles gar nicht verstehen. Es ist aber
auch beim Verstehen ja immer so eine Sache,
mit was ist man groß geworden?
Was ist die Denkmuster und Modelle, mit denen man groß geworden ist,
die man gewöhnt ist und mit denen man keine Schwierigkeiten hat.
Und dies ist also ein etwas anderes Herangehen an die Programmierung.
Und ich hatte ja schon gesagt, Abstraktion ist ja
nicht immer was Schlechtes. Abstraktion ist ja auch etwas,
was das Leben erleichtern kann
Und insofern ist die Arbeit mit der Logikprogrammierung auch ein Nachdenken darüber,
wo tatsächlich Vorteile in diesem Paradigma sind, gegenüber den anderen Paradigmen,
wo die anderen Paradigmen möglicherweise noch Nachholebedarf haben.
Wir haben Werkzeuge und recht mächtige Werkzeuge der Informatik als Basiskonstrukte der Logikprogrammierung im System verankert.
Das ist also der Begriff der relationalen Datenbanken, auf den
wir aufbauen werden. Das ist die nicht-deterministische Suche,
die ich in jeder anderen Programmiersprache, nicht in jeder anderen,
aber in der weitaus überwiegenden Zahl der programmiert mir Sprachen hinzufügen muss,
wenn ich sie denn haben will.
Hier ist sie eingebaut und der Grundberechnungsmechanismus basiert darauf,
so etwas wie Mustervergleich in einer sehr allgemeinen und sehr mächtigen Art und Weise,
die es uns dann im Endeffekt auch erlaubt,
den Unterschied zwischen Daten und Programmen vollständig aufzulösen.
In einer Weise, wie es in keinem anderen Paradigma möglich ist.
Wir haben einen ganz unmittelbaren Bezug zu wichtigen Konzepten der theoretischen Informatik und auch der Mathematik.
Wir in der Übung ist bereits Diskussion aufgetreten,
was sind Relationen, was sind Funktionen.
Wir werden im Begriff der Unifikation hier benutzen, der Variablen-Substitutionen,
der Subsumption von Thermen. Was ist ein Verband, ja,
Subsumption definiert immer eine partielle Ordnung und damit eine Verbandsstruktur.
Auf die Art und Weise bin ich in der Lage,
mit partiell spezifizierten Datenstrukturen zu arbeiten,
das läuft unter dem Begriff der Unterspezifikation.
Wir werden uns Beispiele für Anwendungen angucken im Bereich der Transducer im Bereich der Grammatiken.
Wir werden über die Eigenschaften von Relationen reden,
wie Symmetite Transitivität und der Ableitungsbegriff der Logik taucht ja auch auf.
Also was man hier hat, sind Begriffe, an denen
man unter Umständen gegebenenfalls in der Mathematik oder der Informatik verzweifelt.
Hier kann man damit spielen und kann damit arbeiten.
Wir werden sehen, dass wir relativ kompakte Problemlösungen bekommen.
Das sollte schon deutlich geworden sein bei diesem kleinen Member-Beispiel,
was ich in der letzten Vorlesung benutzt habe
Wir werden oftmals einen sehr direkten Bezug zur Problemstellung haben.
Das ist ein ganz großer Vorteil in vielen anderen Paradigmen,
verschwindet die Problemstellung hinter syntaktischem Overhead.
Der syntaktische Overhead ist hier auf ein Minimum reduziert und
deshalb kann ich in vielen Fällen am Programm direkt erkennen,
was ist eigentlich die Problemstellung, was sind die Eigenschaften der Problemstellung,
welche Lösungsansätze stehen zur Verfügung.
Und das Ganze wird dann offen oder kann benutzt werden,
um funktionsfähige Prototypen relativ schnell zu realisieren. Ich hatte es schon gesagt,
in diesem Jahr werden wir nicht in der Lage sein,
das Praktikum anzubieten, aber beim letzten Praktikumsdurchlauf,
wer in der Expo war, vier Entwickler,
Zwei Wochen Arbeit und es ist ein rundum Vorzeigparapototyp entstanden.
Und alle Vorurteile, die man immer so findet,
Logikformierung ist nicht geeignet, um grafische Oberflächen zu realisieren,
möge sich das bitte angucken.
Und das eine hellige Urteil aller Teilnehmer ist bisher immer gewesen,
okay, das System, was dort verwendet wird, ist krank.
Das ist auch alt, das ist über lange Zeit nicht
gepflegt worden. Aber im Vergleich zu anderen Ansätzen,
wenn ich es also in Java programmiert hätte,
hätte man sich die Finger wund programmiert,
um den gleichen Effekt in der gleichen Zeit
zu erzielen
Das sind also relativ dichte Problemlösungen. Und wenn es wirklich nur darum geht,
so ein Prototyp hochzuziehen und mal zu demonstrieren,
dann ist das durchaus eine gute Wahl auf so ein Paradigma zurückzugreifen.
Wir haben bei der Logikprogrammierung eine berechnungsuniversuelle Programmiersprache und zwar kombiniert,
erreichen wir die Berechnungsuniversalität dadurch, dass wir wenige,
aber dafür relativ leistungsfähige Basiskonzepte miteinander vergleichen.
Das ist eben einerseits der Begriff der Unifikation,
der wird uns über die nächsten Vorlesungen immer zentral begleiten.
Das sitzt der Begriff der rekursiven Datenstrukturen.
Das ist der Begriff der Thermersetzung und vajablen Substitutionen
Und kombiniert das Ganze mit nicht deterministischer Suche.
Und dann haben wir alle Zutaten da,
die wir brauchen, um eine Touring-mächtige Sprache zu kreieren.
Ein netter Nebeneffekt ist, dass ich sowohl die Syntax,
das geht in vielen Programmieren, nein, ja,
geht in einigen Programmiersprachen, dass sich die Syntax der Programmiersprache verändere.
Das werden wir bei den Operatoren sehen, dass sich die
durchaus beeinflussen kann, wie ich die Strukturen hinschreiben möchte.
Und da kann ich das anpassen an existierende Notationen.
Das kriegen wir im Zusammenhang mit den Operatoren.
Es ist aber auch so, dass wir die Semantik
der Sprache über Metaprogrammierung sehr einfach modifizieren können.
Und damit ein spezielles Verhalten für spezielle Benutzungsbedingungen
erreichen können
Und auch da wieder ist die Hauptanwendung experimentelles Prototyping,
dass ich eben ein existierendes System nehme und durch gezielte Variationen der Semantik neue Eigenschaften hinzufüge,
die existierenden Eigenschaften gezielt verändere und schaue,
ob das in Bezug auf eine konkrete Anwendung Vorteile bringt oder eher nicht.
Gut, und damit können wir auch voll einsteigen,
wer das schon mehrfach angekündigt, das erste zentrale Thema,
sind die relationalen Datenbanken. Wir müssen uns mit dem Relationenbegriff
auseinandersetzen. Wir werden dann Fakten als eine Möglichkeit sehen,
Relationen in bestimmter Weise zu implementieren. Wir brauchen natürlich,
wenn wir eine Datenbank haben, auch Anfragen an die Datenbanken.
Das machen wir in verschiedenen Schwierigkeitsstufen. Wir führen als erstes nur Ja-Nein-Anfragen ein,
neben deinen Anfragen mit Variablen hinzu und schließlich noch komplexe Anfragen.
Und um das Thema rund zu machen,
kommt hier noch ein Abschnitt, der eigentlich viel,
viel logischerweise viel später in dem Ablauf eingeordnet werden sollte,
aber da er für viele Zwecke relativ praktisch ist,
bringe ich das an der Stelle schon mal
einen ersten Blick in die Welt der Prädikate zweiter Ordnung
Wir werden uns dann im Zusammenhang mit den metallogischen Prädikaten nochmal
dem Thema genauer widmen. Also das ist hier im Prinzip,
um Ergebnisse aufsammeln zu können, da ist das manchmal ganz nützlich.
Und was wir zum Schluss machen,
ist natürlich ein Vergleich mit der Welt der relationalen Datenbanksysteme.
Denn wir haben hier natürlich kein Datenbanksystem vorliegen.
Wir haben ein System vorliegen, was Logikprogrammierung kann.
Aber das Konzept der relationalen Datenbanken steckt in beiden drin.
Und dann müssen wir uns angucken,
was ist eigentlich der Unterschied zu der relationalen Datenbanksystemwelt
So, also was ist eine Relation? Wir gucken in die Mathematik
und finden dort in irgendeinem beliebigen Handbuch eine solche Definition,
eine Teilmenge des Kreuzprodukts, einer Menge M mit sich selbst.
Da muss man fragen, was ist ein Kreuzprodukt?
Was ist eine Teilmenge eines Kreuzproduktes? Also ein Kreuzprodukt einer
Menge mit sich selbst ist eine Menge von geordneten Paaren,
wobei das erste Element aus der ersten Menge stammt dann das
zweite Element aus der zweiten Menge stammt.
Also das ist relativ simpel und wir können jetzt aus dieser Menge,
die so spezifizierbar ist,
uns eine Teilmenge aussuchen und können ihr einen Namen geben und
dann haben wir
eine Relation
Wir können über die die Menge, über der die Relation
definiert ist, bezeichnen wir auch als Domäne der Relation.
Und in diesem Abschnitt der relationalen Datenbanken unterhalten wir uns
nur über sogenannte extensionale Spezifikationen einer Relation.
Das heißt, Spezifikation durch Aufzählen ihrer Elemente.
Das ist genau das, was in relationalen Datenbank gemacht wird.
Ich gebe Tupel in die Datenbank ein und die beschreiben mir,
was gehört zur Relation und was gehört nicht dazu.
Also das, was nicht dazu gehört,
ist die Komplimentmenge dazu
Also ein einfaches Beispiel. Wir haben hier eine Menge von
drei verschiedenen Kreistypen gegeben, kleine, mittlere und große.
Wir bilden das Kreuzprodukt darüber und dann haben wir natürlich neun solche Paare,
die jeweils aus den Elementen dieser Menge gebildet werden.
Und jetzt können wir hingehen und können über diesem Kreuzprodukt
Teilmengen uns auswählen nach irgendwelchen Kriterien,
die wir haben und wir können ihnen einen Namen geben.
Also wir können, die Relation ist gleich groß,
definiert in der Menge A,
dann durch die Auswahl aller gleich großen Kreise definieren.
Exzensionale Spezifikation,
wir zählen die Elemente auf
Später werden wir dann dazu kommen, wie wir die Relation
tatsächlich berechnen können. Denn solange wir sie aufzählen müssen,
sind wir immer gezwungen, über endliche Mengen zu reden.
Wir wollen irgendwann mal auch über unendliche Mengen reden können.
Wenn wir sowas wie über natürliche Zahlen reden,
dann müssen wir über unendliche Zahlen reden. Über unendliche Mengen
reden. Gut, also immer Paare von gleichgroßen Kreisen ausgesucht.
Das ist eine Teilmenge aus dem Kreuzprodukt der Menge A mit
sich selbst und demzufolge eine Relation in A. Oder eben die Menge ist kleiner als,
dann sind es diese drei Paare,
der kleine Kreis ist kleiner als der mittlere,
ist aber auch kleiner als der Große und der mittlere
ist kleiner als der Große
Oder aber eben die Kombination, nein, ist nicht die Kombination,
aber die Kombination aus einer Ordnungsrelation und einer Äquivalenzrelation,
also größer oder gleich, die beiden sind gleich.
Das ist größer als das, das ist größer als das.
Die beiden sind wieder gleich und dann ein noch,
der Große ist größer als der Mittlere und die beiden Großen sind gleich groß.
Das heißt also, hier haben wir eine Relation mit der Kardinalität 6,
es gibt sechs Paare,
die innerhalb in der Relation über dieser gegebenen Menge,
die dazugehören,
die sind beide Relationen der Kardinalität
So, für praktische Zwecke ist das natürlich noch nicht so richtig geeignet.
Wir müssen uns also überlegen, wie wir erstens den Begriff erweitern.
Das kommt als nächstes. Und zweitens, was machen wir
mit solchen Relationen eigentlich? Also Relationen sind immer Beziehungen zwischen
Objekten. Das haben wir gesehen. Die Beziehung heißt,
ist kleiner als und wir haben zwei Objekte, zwischen denen diese Beziehung gilt.
Und das müssen nicht Kreise sein, das können Ereignisse sein,
abstrakte Ideen, Begriffe, was auch immer wir da haben.
Wir können also Relationen der Art, wie dieser,
diese Idee ist cooler als die andere aufbauen.
Und wir können eine Datenbank davon aufbauen und dann Anfragen an diese Datenbank stellen.
Die Relation selber ist ein rein formales Konstrukt.
Wir haben es aus der Mathematik gebeugt. Das ist also
rein formal. Wir wollen aber praktische Aufgabenstellungen damit lösen.
Und damit sind wir mitten in dem Problem der Modellierung drin.
Wir haben ein formales Handwerkszeug. Wir haben ein gegebenes Realweltproblem
und wir müssen die Brücke dazwischen bauen.
Und das ist die Modellierung, die wir in der Informatik
an auf allen Ebenen
immer wiederfinden
Wir haben dann das Problem, dass solche Objekte und Begriffe niemals isoliert auftreten,
sondern in einem großen Bedingungsgefüge verankert sind. Also was weiß ich,
dieser Stuhl hier, ist eben in dem Bedingungsgefüge verankert,
von wem ist er mal hergestellt worden,
wann ist er hier montiert worden,
in welchem Raum befindet er sich, welchen Zweck dient er,
aus welchem Material ist er hergestellt und so weiter und so fort.
Und wir müssen, wenn wir ein volles Verständnis,
was illusorisch ist, das gibt es nicht,
aber wenn wir ein umfassendes Verständnis dieses Objektes haben wollen,
müssten wir all diese Relation irgendwie erfassen.
Und das geht natürlich nicht
Und wir müssen irgendwie eine Auswahl treffen. Was ist für
eine bestimmte Aufgabenstellung notwendig und was ist nicht notwendig,
sodass wir also diesen Begriff aufweichen müssen,
statt vollständiger Erfassung, die typischerweise gar nicht geht,
weitgehende Erfassung und das immer unter einem bestimmten und einer bestimmten Aufgabenstellung.
Also wir müssen immer fragen, was sind die wesentlichen Relationen,
die diesen Stuhl hier charakterisieren?
Und damit sind wir mitten im Thema der Abstraktionen drin,
wir müssen einen wichtigen Abstraktionsschritt machen von diesem konkreten Ding aus der Realwelt,
müssen wir zu einer Beschreibung in Form von Beziehungen,
also von Tupeln
Ja, im strengen Sinn der Mathematik ist eine Relation immer etwas zweistelliges.
Also wir hatten Beispiele schon gesehen, A ist kleiner als B,
A passiert zeitlich nach B, also eine zeitliche Ordnung.
A ist Voraussetzung für B, eine Art Kausalkette, die man aufbaut.
Und wir werden diesen Fall jetzt erweitern auf einerseits endstellige Relationen,
die wir in einer natürlichen Sprache auch haben.
Also A liegt zwischen B und C, A verkauft
B an C, also dreistellige gibt es noch relativ viel.
Bei vierstellige muss man schon sehr genau suchen,
wenn man die tatsächlich finden will.
Also so etwas wie A transportiert B von C nach D,
das wäre eine vierstellige Relation.
Oder aber A übersetzt
B, von Sprache Zehnersprache D, das wäre auch eine vierstellige Relation,
aber davon gibt es da nicht mehr allzu viele.
Und wir haben noch den Spezialfall der Einstelligen Relation,
die in der Mathematik auch als Eigenschaften oder Prädikate bezeichnet werden,
also so etwas wie A, ist groß, A,
ist blau, A, ist ein Haus.
Solche einstelligen Prädikate werden in unserer normalen Sprache immer
durch Adjektive oder durch Substantive realisiert,
die höherstelligen Relationen meistens in Form von Verbkonstruktionen
So, solche Relationen wollen wir jetzt natürlich auch abbilden und
da stoßen wir bei den zweistelligen Relationen über einer Menge schnell an die Grenzen.
Hier ist ein solches Beispiel, wir haben eine Menge von Personen,
drei Personen, wir wollen eine Relation,
ist Mutter von in P definieren und wir müssen eben die Paare von Personen aufzählen,
die in dieser Relation ist Mutter von Stehen.
Aber das ist eben zu restriktiv für viele praktische Anwendungen.
Meistens wollen wir ja über anspruchsvollere Dinge reden.
Also der erste Schritt, wir verallgemeinern das auf unterschiedliche Domänen.
Wir sahen jetzt nicht mehr ein Kreuzprodukt,
eine Menge mit sich selbst, sondern das Kreuzprodukt zweier beliebiger Mengen
Und da haben wir zwei beliebige Mengen, weiße,
geometrische Figuren, schwarze geometrische Figuren.
Und wir können über diesen beiden oder zwischen diesen beiden Mengen
eine Relation definieren, ist kongruent zu.
Und dann haben wir eben Kreise,
sind zueinander kongruent, unabhängig von der Farbe.
Quadrate sind kongruent, Dreiecke sind kongruent zueinander.
Also wieder eine Teilmenge aus diesem Kreuzprodukt hier ausgewählt,
ist dann die Relation. Und zwar ist es eben jetzt
nicht mehr über dem Kreuzprodukt der Menge mit sich selbst,
sondern das Kreuzprodukt zweier unterschiedlicher Mengen
Oder wir haben hier eben, hier sind die Typen noch gleich,
hier kamen wir unterschiedliche Typen, dass wir Namen mit Zahlen
kombinieren in einer Relation, hat ein bestimmtes Alter.
Und dann können wir durch Aufzählen der Paare aus Personen und Alter
eben eine Teilmenge des Kreuzproduktes aus Personen und Altersangabe modellieren.
Der zweite Schritt ist, dass wir nicht nur zweistellige Relationen haben wollen,
sondern höherstellige Relationen, dass wir also jetzt nicht nur zwei Mengen miteinander multiplizieren,
sondern beliebig viele. Und ein Beispiel ist eben,
dass wir hier die Personen mit ihren Altersangaben und einem Familienstand
Informationen im Miteinander kombinieren wollen und dann eben eine Relation über drei,
mit drei Argumenten haben.
Eine Person hat ein Alter und einen bestimmten Familienstand und hier
stehen eben die drei Tupel, die Bestandteil, die Sarelationen sind.
So, das wollen wir jetzt natürlich für ein etwas praxisnäheres Beispiel haben.
Hier ist ein kleiner Ausschnitt aus einer Tabelle,
wie man sie im MySQL etwa aufbauen kann für Immobilien.
Die haben einen Identifier hier vorn, die haben einen Typ
von Einfamilienhaus bis zum Mehrfamilienhaus Bahnhof oder Kaufhaus.
Die haben eine Adresse, bestehend aus Straßen- und Hausnummernangabe und
sie haben ein Baujahr
Und auf die Art und Weise kann man eine Datenbank von Immobilien aufbauen.
Und diese Datenbank wollen wir jetzt im Prolog-System verarbeiten.
Und was wir dafür brauchen, es ist ein Sonntagskonstrukt.
Das Syntagskonstrukt sieht etwas anders aus, als wäre das von SQL etwa gewöhnt sind.
Es ist aber auch die Sache sehr stark kondensiert und auf den Kern gebracht,
während also das Modell der relationalen Datenbanken in der Datenbankwelt davon ausgeht,
dass jedes Attribut einen Namen hat,
also jede Tabellenspalt einen Namen hat.
Das haben wir eben gesehen. Hier oben drüber steht immer
der Attributname
Machen wir jetzt die Vereinfachung, dass wir den Attributnamen weglassen?
Das ist wieder eine Abstraktion, die wir machen.
Und wir ordnen die Informationen über die Positionen in einem Tupel zu.
Dass wir also wissen, die erste Argumentstelle ist,
der Identifier, die zweite Argumentstelle ist,
der ist das Einfamilienhaus und so weiter und so fort.
Der Unterschied besteht jetzt darin, dass dies ein geordnetes Tupel ist hier,
während die Tupel in der relationalen Datenbankwelt ungeordnet sind.
Die relationale Datenbankwelt macht an keiner Stelle eine Annahme darüber,
in welcher Reihenfolge die Tupel geordnet sind und in welcher Reihenfolge
die Attribute geordnet sind.
Das ist frei
Das ist, darüber hat ja der Datenbankprogrammierer keine Gewalt.
Das ist das Privileg des Systems, das festzulegen. Hier ist es anders,
hier vereinfachen wir das jetzt zu einem geordneten Tupel aus diesen Attributen
und wir müssen dazu noch den Namen der Tabelle angeben,
also den Prädikatsnamen werden wir das in unserer Welt nennen.
Und ein solcher Ausdruck, der besteht aus Prädikatsnamen,
öffnender und schließender Klammer und durch Komma getrennte Attribute,
das nennen wir einen Fakt.
Und ein Fakt ist eben eine oder bildet elementare Klauseln,
die genau eine Grundstruktur enthalten
Wir vereinfachen das im Augenblick Grundstruktur heißt eine Struktur, deren Argumente atomar sind.
Das werden wir später erweitern, dass dort auch wieder Strukturen zugelassen sind.
Und damit haben wir ein bisschen Syntax hier,
also eine Klausel, kann entweder ein Fakt sein,
es kann auch noch etwas anderes sein, diese drei Punkte hier bedeuten immer,
es gibt auch noch andere Varianten, die kommen später.
Und das Ganze ist wichtig, muss von einem Punkt abgeschlossen sein.
Also das ist das Allererste,
wann immer ihr Produkt-System keine Reaktionen zeigt,
ist in den meisten Fällen der Grund,
dass sie den Punkt vergessen haben
Eine Klausel, egal welcher Art, muss immer durch einen Punkt
abgeschlossen werden. Und wenn man das am prompt macht,
dann bleibt das System so lange stehen,
bis es den Punkt irgendwann mal kriegt.
Wenn man das in einem Programmtext macht, der eingelesen wird,
dann gerät meistens die Syntaktische Struktur zwischen aufeinanderstehenden Klauseln durcheinander.
Und das führt dann zu Folgefehlern. Also wichtig ist der Punkt immer hier hinten,
Fakten sind dann eben eine einfache Struktur und eine Struktur
besteht immer aus ihrem Namen und einer öffnenden optional.
Es gibt auch nullstellige Strukturen, optional einer öffnenden und schließenden
Klammer. Und in dieser Klammer sind Therme durch Komma getrennt
Und für Therme vereinbaren wir im Augenblick, das wird später wieder erweitert.
Im Augenblick, das ist nur Konstanten sein dürfen. Und Konstanten,
da lassen wir zu Zahlen, Namen oder gequotete Namen,
gequotete Namen brauchen wir, um später,
um Namen zuzulassen, die auch mit einem Großbuchstaben beginnen.
Denn das ist syntaktisch reserviert, das kriegen wir später bei
den Bajablen. Wenn wir darauf Wert legen,
dann müssen wir
Hochgemeister umsetzen
Aber das ist so ein taktisches Kleingemüse.
Ein Name beginnt also immer mit einem kleinen Buchstaben und danach
kann dann eine große Auswahl alphanumerischer Symbole folgen.
Nicht alles, weil es natürlich reserviert ist, Symbole gibt,
so etwas wie der Punkt und das Kommada 4 drin auch nicht auftreten.
Aber ansonsten ist es relativ viel. Man sollte also immer sehen,
dass es mit Operatoren, von denen man weiß,
dass in der Programmiersprache auch enthalten sind, nicht kollidiert.
Also es ist immer riskant, ein Minus dort reinzuschreiben.
Es könnte als Operator Minus verstanden werden
Das ist die Syntax dazu. Wir können jetzt also Fakten,
syntaktisch konstruieren, wir brauchen eine Semantik dazu und diese Mantik ist relativ simpel.
Jeder ein Fakt, den wir auf die Art und Weise
ins System eingeben, ist genau ein Element einer Relation.
Wir spezifizieren ja eine Relation durch aufzählende Elemente und einen Fakt,
den wir hinschreiben, ist das Element der Relation mit dem
jeweils angegebenen Prädikatsnamen und der Stelligkeit, das heißt,
der Anzahl dieser Argumente, die wir hier dahinter spezifizieren.
Und wichtig, das geht in die Semantik des Ganzen
mit ein, es, Strukturen unterschiedlicher Stelligkeit sind unterschiedliche Relationen
Prädikat kommt gleich liebt, kann ich einstellig, kann ich auch
zweistellig machen oder liest beispielsweise, dann sind das unterschiedliche Prädikate.
Also ich kann sagen, Hans singt ein Lied,
dann ist es ein zweistelliges, zweistellige Relation oder ich kann sagen,
Hans singt, dann ist es eine einstellige Relation.
Das sind im Polux-Sinne unterschiedliche Prädikate, die dort definiert werden.
Wir hatten also dieses Häuserprädikat fünfstellig mit dem Namen Obch für Immobilienobjekt liebt beispielsweise ein zweistelliges Prädikat,
wer liebt was, Teil von, ist eine zweistellige Relation,
ein Rad ist Teil eines Autos und so weiter und so fort.
Und damit wir also solche Fälle sauber unterscheiden können, müssen wir,
wenn wir eindeutig identifizieren wollen, über welches Prädikat reden wir eigentlich,
müssen wir die Stelligkeit immer mit angeben. Ansonsten kann es sein,
dass das mehrdeutig ist und auch das Prologsystem kennt bei seinen eingebauten Prädikaten verschiedene Varianten,
wo eine ähnliche Semantik mit unterschiedlicher Stelligkeit realisiert wird,
dass es meistens dann der Fall, wenn noch eine zweite Variante existiert,
wo ich den Pointer auf das entsprechende Datenobjekt direkt mit angeben kann.
Aber das ist etwas, was wir hier überhaupt nicht behandeln. So,
jetzt können wir also hingehen und können unsere vorher in SQÖL-Ausdruck gegebene Datenbanktabelle umschreiben in die Prologsyntax,
indem wir also Fakten zur Relation,
ob fünfschnellig schreiben und das sind exakt die gleichen Informationen,
die wir vorher in dieser Tabelle drin hatten,
mit Identifier-Typ, Straßenangabe,
Hausnummern,
Angabe und Baujahr
hier hinten
Da wir den Schritt gemacht haben,
dass wir die Attributnamen nicht explizit hier spezifizieren,
müssen wir jetzt natürlich irgendwo dokumentieren, was bedeutet diese Information hier eigentlich?
Also ist das hier vorne die Hausnummer oder ist das hier hinten die Hausnummer?
Das sieht man den Dingen ja nicht an oder vielleicht ist das
hier hinten die Hausnummer oder es ist der Preis oder was auch immer.
Den Daten selber sehe ich nicht an, welche Bedeutung sie
haben und für die zwischenmenschliche Kommunikation ist das aber wesentlich.
Wir müssen uns also irgendwo dokumentieren,
was meinten wir damit und deshalb sollen sich von Anfang an daran gewöhnen,
dass wir immer Kommentar zu einer Prädikatsdefinition dazu schreiben,
der uns jetzt, der mit uns sagt,
wie viele Stellen hat diese Relation und was ist unsere,
unser Verständnis von dem, was dort codiert ist.
Also hier die Objektnummer, der Objekttyp, die Straße,
die Hausnummer, das Baujahr.
Das dient nur der zwischenmenschlichen Kommunikation. Deshalb ist das
ein Kommentar. Das Prozentzeichen ist das Kommentarzeichen in Prolog
und dient nur dazu, um verstehen zu können.
Wir werden das also sofort sehen, wenn wir über so
etwas wie Mutter vonreden. Es gibt keine etablierte Standard,
ob das erste Argument oder das zweite Argument
die Mutter davon ist
Ich kann beides gleichermaßen gut begründen. Ich kann also sagen,
A ist Mutter von B. Ich kann aber auch sagen,
die Mutter von A ist B. Ich kann beides in gängige,
natürlich sprachliche Varianten übersetzen und das ist dem formalen Ausdruck nicht
zu entnehmen, wie rum ist er gemeint.
Und deshalb sollte man sich immer daran gewöhnen,
dass man dies sofort mit hinzuführt, äh hinzufügt,
damit diese Interpretation klar ist.
Ja, wir können jetzt also solche Prädikatsdefinitionen,
indem wir einfach Fakten untereinander schreiben,
in eine Datei schreiben und die Datei in das System einlesen.
Das ist also Gegenstand
Der des nächsten Übungszettels,
dass sie das einfach mal machen, sich anschauen,
was passiert im System und sich auch schon mal anschauen,
wie kann man diese Datenbank interaktiv verändern.
Und ja, das sind die wesentlichen Dinge.
Natürlich speichern wir die Sachen in dem System nur zu dem
einzigen Zweck, dass wir sie wiederfinden können.
Einen anderen Gründen gibt es nicht. Und deshalb brauchen wir
so etwas wie eine Anfrage an die Datenbank.
Das wird je nach Schule unterschiedlich behandelt.
Oftmals wird es als Ziel bezeichnet oder dann eben Englisch Goal
Das ist wieder eine elementare Klausel, genau wie die faktenelementare Klauseln sind.
Nur, dass der einzige Unterschied ist, nicht so ein taktischer Natur,
sondern ist die Frage, wo wird sie verwendet?
Während wir also Fakten etwa über das Einlesen aus einer Datei
in die Datenbank eingeben können, tauchen Anfragen im Moment,
das wird sich noch beändern,
aber im Moment nur am System prompt auf.
Wenn ich also am System prompt, einen einfachen Fakt eingebe,
dann hat er die Bedeutung nicht, das ist so,
sondern die Bedeutung stimmt das,
was hier steht
Also, gibt es ein Objekt,
was die Nummer eins hat, was ein Einfamilienhaus ist,
was in der Gärtnerstraße steht,
was die Hausnummer 15 hat und was 1965 gebaut worden ist.
Ist diese Aussage wahr, ja oder nein?
Oder mit anderen Worten ist dieses Tupel-Element der Relation,
die wir vorher spezifiziert und eingelesen haben.
Also hier, das ist der Inhalt einer Datei.
Wir lesen diese Datei mit Consult in das System ein.
Das sind Fakten, die wir der Datei mitteilen.
Und Consult heißt, nimm Fakten,
später werden es auch Regeln sein,
nimm solche Fakten aus der Datenbank entgegen
Und wenn ich dann hinterher am System prompt, was aussieht,
wie ein Fakt eingebe, dann hat er die Bedeutung,
eine Anfrage. Und die Syntax ist dann eben genauso,
wie wir das bisher hatten, ein solches Ziel,
kann ein elementares Ziel sein und ein elementares Ziel ist eine Struktur.
Und was eine Struktur ist, das hatten wir vorher schon,
das ist nämlich ein Ding, was aus Prädikatsnahme,
Klammer auf, Klammer zu und N-Argumenten besteht.
Diese Mantik einer solchen Anfrage ist, prüfe bitte,
ob das, was hier steht,
konsistent ist mit dem,
was in der Datenbasis
schon abgespeichert ist
Das ist der elementarste Fall einer Unifikation. Und das,
was der Prozess, der dort abläuft, ist eine Ableitung.
Ich versuche also, dieses Theoriem hier aus den Axiomen der Datenbank abzuleiten.
Nur sind das alles triviale Dinge, die bis jetzt da drin stehen.
Das wird aber komplizierter. Also ich versuche zu beweisen,
dass diese Aussage stimmt, gegeben die Informationen,
die in der Datenbank zu finden ist.
Und das passiert alles unter einer Annahme der abgeschlossenen Welt.
Und diese Annahme der abgeschlossenen Welt heißt immer die Beschreibung,
die in der Datenbank steht,
ist eine vollständige Beschreibung der Welt
Das ist natürlich nicht realistisch. Jede Beschreibung,
die ich habe, ist unvollständig. Aber für die Zwecke
der formalen Verarbeitung machen wir hier diese Annahme. Alles,
was wir wissen, über die Welt steht in der Datenbank.
Und wenn jetzt also dies, was hier gefragt wird,
sich nicht in der Datenbank finden lässt,
dann kommt das No heraus oder ein Falls.
Das heißt, die Aussage ist nicht,
das stimmt nicht, sondern dieses Falls heißt,
in der Datenbank konnte ich dazu
nichts passendes finden
Es kann immer noch sein, dass die Datenbank unvollständig ist.
Da muss man, die Datenbank ist nicht die Realität,
sondern eine Beschreibung der Realität. Das muss nicht identisch sein
mit der Realität. Da können Fehler drin sein.
Und insbesondere kann Informationen fehlen. Also False heißt nicht,
das ist falsch, sondern heißt nur,
es geht aus den Axiomen der Datenbank nicht ableitbar.
Ich habe in der Datenbank keine relevante Informationen gefunden,
die mir zu dieser Anfrage irgendetwas aussagt.
Im Übrigen, wenn Sie hingucken, genau hingucken,
Nee, die steht eins zu eins so drin.
Also in diesem Falle sollte man erwarten, dass dort ein Truh herauskommt.
Also ja, das stimmt, was hier gefragt worden ist.
Wir müssen, wenn wir über Semantik reden,
über zwei Semantikbegriffe immer sauber unterscheiden. Das ist,
wer die funktionale Programmierung bei Frau Dreschler gemacht hat.
Für den ist das Schnee von vorgestern.
Wir haben immer zwei Semantikbegriffe. Das eine ist die sogenannte
Denotationelle Semantik und das zweite ist die operationale Semantik.
Wenn wir über die Denotationelle Semantik reden,
dann haben wir eine Verarbeitungsfreie Sicht auf die Ergebnisse
Wir spezifizieren, was das Berechnungsergebnis sein soll.
Wie das System das berechnet, sollte uns vollkommen egal sein.
Das ist eine statische Sicht. Das ist genau die Sicht,
die der Mathematiker zugrunde dicht, der also sagt,
ein Element aus dem Kreuzprodukt. Wie ich dieses Element finde,
gebildet habe oder sonst etwas, das interessiert ihn nicht.
Und er redet eben von Mengen. Mengen haben ja immer
die Eigenschaft, dass sie Elemente nicht doppelt enthalten dürfen.
Also Duplikate treten in Mengen nicht auf.
Auf einem Rechner muss ich
das erzwingen
Dem Mathematiker ist es selbstverständlich, dass das so ist.
Das ist die Definition der Menge. Und deshalb gibt es also mal einen,
oder kann es einen kleinen Unterschied geben zwischen dem mathematischen Begriff
der Ableitung, den wir eben bemüht haben.
Wir wollen aus den Axiomen der Datenbank, die das Theorium
ableiten können. Wenn wir das im mathematischen Sinne betrachten,
abstrahieren wir von allen Berechnungsprozessen, die da ablaufen müssen.
Wenn wir uns auf die Ebene des Rechners begeben,
müssen wir die operationale Semantik benutzen.
Dann müssen wir uns fragen, wie geht der Rechner vor,
um genau dies zu tun
Also was sind die dynamischen Abläufe, die passieren,
wenn ich diese Ableitung tatsächlich ausrechnen will?
Und dann muss ich über das Verhalten des Programms reden und
nicht nur über welche Ergebnisse gehören zur Ergebnismenge.
Und wir werden sehen oder eine der wichtigsten Aufgaben für die ersten drei,
vier Vorlesungen wird sein, wo ist die Grenze dazwischen?
Wo fällt zum ersten Mal die lenotationelle Semantik auseinander mit der
operationalen Semantik? Oder mit anderen Worten,
wo tut das erste Mal unser Rechner etwas anderes als das,
was wir
eigentlich wissen wollten
Und das ist kein Artefakt der Logikprogrammierung,
sondern das ist ein Artefakt der Programmierung schlechthin.
Diesen Unterschied haben wir in allen Paradigmen, dass wir das,
was wir berechnen wollen, eigentlich nicht berechnen können.
Und der Grund ist eigentlich immer wieder dasselbe.
Unser Programm terminiert nicht. Wir schicken es in eine Endlosschleife
und das können wir in jedem Paradigma machen.
Da ist kein Kraut dagegen gewachsen. Wir können ihn immer
in eine Endlosschleife schicken und in einem solchen Fall ist natürlich das,
was berechnet wird, nicht mehr das,
was es eigentlich berechnet werden sollte
Und da das Ziel ja hier auch ist,
dass wir über unendliche Mengen reden,
werden wir also relativ schnell mit dem Phänomen konfrontiert worden werden,
dass die operationale Semantik nicht mehr identisch ist mit dem,
was wir denotationell eigentlich haben wollen.
Ich kann schon mal beruhigende Pillen austeilen, solange wir über
relationale Datenbankenwelten reden, ist die Welt noch heil.
Es wird erst im Zusammenhang mit der deduktiven Datenbank spannend.
So, also dernotationelle Semantik heißt, folgt das Ziel,
also irgendeine logische Aussage, aus dem Fakten in der Datenbasis.
Das ist unsere Aktion Menge. Also,
wenn wir fragen, ist dieses Objekt,
folgt diese Angabe, dass es ein Haus mit Nummer,
also Objekt Nummer eins vom Typ Einfamilienhaus und so weiter gibt,
aus dem, was in der Datenbank steht und dann kriegen wir ein Truh heraus.
Und wenn wir das nur irgendwo ein bisschen modifizieren,
hier hinten das Baujahr etwas modifizieren,
dann kommt dieses Falls heraus und wir müssen immer in Rechnung stellen,
dieses negative Resultat bezieht sich nur auf die gegebene Datenbasis,
aber nicht auf die reale Welt.
Operational heißt es immer,
wir müssen
das Ergebnis suchen
Wir müssen gucken, ob in der Datenbank irgendwo
eine Eintragung drin ist, die mit der Anfrage übereinstimmt.
Und das können wir nur machen,
indem wir die Einträge der Datenbank Stück für Stück durchgehen.
Natürlich kann man Indexstrukturen darüber aufbauen und das wird auch gemacht.
Aber im Endeffekt ist es eine Suche. Also wir können uns
die Suche erleichtern. Wir werden sie aber nicht bloß.
Und zwar ist es hier die Suche nach einem unifizierbaren Aktionen in der Datenbasis.
Da ist dieser magische Begriff der Unifikation wieder.
Da müssen wir uns genauer angucken, was das eigentlich heißt
Zuerst zum Suchbegriff, dann zum Unifikationsbegriff.
Suche heißt immer Systematisches durch, Mustern von Entscheidungsalternativen.
Wir haben also hier sieben Fakten gegeben und wir müssen systematisch durchgehen,
passt irgendeiner dieser Fakten auf die Anfrage?
Die Suchstrategie ist dann die Festlegung, in welcher Reihenfolge machen wir das?
Gehen wir von unten nach oben vor oder von oben nach unten?
Oder haben wir irgendeine andere Verteilung darüber? Und wenn die Ableitung,
die ist in diesem Falle nur ein einziger Schritt,
wenn die aus mehreren Schritten besteht, dann gehört zur Suchstrategie auch,
in welcher Reihenfolge betrachten wir die Schritte?
Für Prolog wird die Vereinbarung immer gelten,
wir durchsuchen die Datenbasis von oben nach unten.
Das ist natürliche Sache,
alles andere wäre kurios
Und wenn wir ein negatives Resultat erhalten, heißt das immer,
die Suche war erfolglos. Wir haben die Datenbasis vollständig durchgesucht
und haben nichts passendes gefunden. Oder nichts passendes noch zusätzlich.
Wenn wir mehrere Ergebnisse haben, können wir die Ergebnisse natürlich abfragen.
Und wenn dann das System irgendwann mal vor uns sagt,
dann heißt das nicht, es gibt kein Ergebnis,
denn ich habe schon fünf Ergebnisse gesehen, die passen,
sondern dann heißt es, ich habe kein weiteres Ergebnis gefunden.
So, und dann kommen wir zum Begriff der Unifikation.
Hier oben diese Römisch 1 soll schon andeuten,
das ist nur die erste Sicht darauf.
Wir werden diesen Begriff der Unifikation
in mehreren Schritten erweitern
Auf immer komplexere Datentypen hin. Hier haben wir erst mal den elementarsten Fall.
Unifikation heißt immer, überprüfe die Verträglichkeit zweier Strukturen.
Eine Struktur war immer so ein Ding mit einem Prädikatsnamen und N-Argumenten dahinter.
Also in unserem konkreten Fall ist die Semantik also,
weil wir Grundstrukturen haben, wo nur Atome drinstehen,
ist das ein Identitätstest.
Wir prüfen, ist der Prädikatsnamen identisch,
ist die Stelligkeit identisch und sind die Argumente paarweise identisch
Und das wird genau das Schema sein, wo wir Erweiterungen vornehmen müssen,
identischer Prädikatsnahme und gleiche Stelligkeit bleibt immer erhalten,
aber paarweise identische Argumente, das wird dann zu erweitern sein,
auf komplexere Zustände.
Und im Endeffekt kommt dann das heraus, was wir eben gesehen haben,
es ist ein simpler Vergleich des Ziels mit den Fakten der Datenbasis.
Interessant wird es erst, wenn die Strukturen Vagablen enthalten,
aber die haben wir noch gar nicht.
So, wir machen eine Beispieldatenbasis, die etwas einfacher ist,
als die Hausdatenbank,
die enthält nämlich
nur vier zweistellige Fakten
Zu der Relation liebt zweistellig und das ist auch nicht sehr richtig typ rein.
Also hier ist es teilweise Personen, lieben Personen,
aber ab Personen lieben auch Gegenstände oder eben abstrakte Dinge wie Geld.
Zur Programmiermethodik sollte man also immer sagen,
dass man auf jeden Fall Kommentare zu dieser Prädikatsdefinition angeben sollte,
über das Prädikatsschema hatte ich schon einiges gesagt,
aber man sollte auch Zusicherungen machen über die zulässigen Argumentbelegungen.
Und das habe ich hier mal versucht,
das ist ein bisschen klobig geraten,
also das Prädikatschema ist zweistellig,
wir haben eine Angabe,
wer liebt und wen oder was
wird geliebt
Und dann macht man das eben deutlich,
dass wer die Person ist, der die Person ist,
die liebt und das unterwählen oder was, das steht,
was gelebt wird.
Das muss man deutlich machen. Ansonsten ist in solch einer,
auch in diesem Falle nicht erkennbar, wer ist Argens,
wer ist Party in dieser Handlung,
also wer handelt und wer auf wen wirkt sich die Handlung aus.
So, und jetzt gucken wir uns an,
wie eine Anfrage tatsächlich abgearbeitet wird.
Das ist in diesem Fall alles noch trivial. Aber dieser Ablauf,
den gucken wir uns im trivialen Falle an,
wird im komplexen Fall exakt der gleiche sein
Es wird, wir müssen noch ein paar zusätzliche Fälle betrachten,
aber das Grundschema bleibt immer vollständig identisch.
Wir schauen uns hier die operationale Semantik an,
denn es geht um die Abarbeitung der Anfrage und da gibt es zwei Möglichkeiten,
sich das zu visualisieren. Das eine ist ein Trace,
das System stellt auch ein Trace zur Verfügung und das zweite wird sein,
in dass wir uns den Suchraum, den das System durchmustert,
versuchen zu visualisieren. Also wir stellen die Anfrage,
liebt Hans Bier und mit dieser Anfrage geht die Suche in der Datenbank,
die diese vier Eintragungen enthält, mit der ersten Datenbank,
stellt sie fest, dass hier hinten ein Konflikt ist, damit scheitert die der vergleicht.
Oder die Unifikation zwischen diesem Datenbank-Eintrag und dieser Anfrage scheitert. Dann nimmt man den zweiten Datenbank-Eintrag,
das scheitert auch hier hinten auf der zweiten Argumentstelle,
da nimmt man den dritten Art Datenbank-Eintrag,
das scheitert auf der ersten Argumentstelle und dann nimmt man den vierten Datenbank-Eintrag,
unterscheitert auch auf der ersten Argumentstelle und deshalb kommt insgesamt die Nachricht Falls heraus,
denn ich habe die Datenbank vollständig durchmustert und habe nichts gefunden,
was auf diese Anfrage passt.
Ich kann die Anfrage modifizieren und fragen,
liebt Susi ein Buch, dann klappt es mit dem ersten,
mit dem ersten Fakt nicht, mit dem zweiten Fakt klappt es nicht,
weil hier vorn Hans nicht mit der Susi zusammenpasst, also passen,
schauen vielleicht zusammen, aber es ist nicht identisch, wir machen ja Identitätstest.
Und hier der dritte Fakt, den ich anfasse,
der ist tatsächlich identisch mit der Anfrage und deshalb gibt es einen Success.
Und wegen dieses Successes sagt mir das System True,
was heißt diese Anfrage ist wahr, so wie sie da steht?
Wir gucken uns die alternative Darstellung als Suchraum an und das ist immer ein Baum.
Der Baum fängt immer mit Startknoten hier oben an.
Das ist unsere Anfrage und die Tochterknoten, die hier unten sind,
das sind die Fakten aus der Datenbasis.
Und da muss ich eben immer vergleichen,
passt diese Anfrage hier oben mit dem Fakt in der Datenbasis zusammen und hier unten ist das Unifikationsresultat.
Also Unifikation von liebt Hans Bier, mit liebt Hans Geld,
scheitert, liebt Hans Bier, liebt Hans Susi,
scheitert und so weiter, liebt Hans Bier, liebt Susi Buch,
scheitert auch und auch der vierte Vergleich scheitert und deshalb kommt insgesamt ein Falls heraus.
Die Datenbank ist vollständig durchsucht, weitere Eintragung gibt es nicht
Jetzt nehmen wir die zweite Anfrage, liebt Susi ein Buch,
die erst mit dem ersten Fakt scheitert die Ulifikation mit dem
zweiten Fakt scheitert die Unifikation mit dem dritten Fakt ist sie
erfolgreich und deshalb kriegen wir ein True als Ergebnis heraus.
Das ist alles simpel. Das ist nicht sonderlich spannend.
Wir haben eine Festlegung getroffen für die Abarbeitung. Wir suchen
die Datenbasis in der Reihenfolge der Eintragung, also von oben nach unten durch.
Das ist genau das, was wir eben beobachtet haben.
Und was wir hier sehen für diese Beispiele,
ist das, was wir denotationell haben wollten und das,
was operational herausgekommen ist, identisch.
Also wenn man sich das nochmal anguckt,
die Datenbank enthält eben keine Informationen über Hans Bier,
also sollte das auch nicht in der, ein positives Resultat liefern.
Und diese Information ist in der Datenbank enthalten,
wird bestätigt durch die Achse Oma, das heißt,
hier ist ein Droh gerechtfertigt.
Das ist genau das, was wir erwarten würden.
Wenn das so ist, dass die Denotationelle und die operationale Semantik
identisch sind, dann sprechen wir von sogenannter Bezugstransparenz.
Dann nennen wir diese Semantik der Sprache referenziell transparent oder deklarativ.
Das definiert eine deklarative Sprache und das gilt eben für die
Reine funktionale Programmierung, genauso wie für die reine Logikprogrammierung,
das referenzielle Transparenz über große Strecken hingegeben ist.
Genauso wie in der funktionalen Programmierung wir uns angucken müssen,
an welchen Stellen fallen die beiden auseinander,
werden wir genau das Gleiche auch in der Logikprogrammierung machen.
Referenzielle Transparenz ist eine sehr schöne Eigenschaft und die bedeutet eben,
insbesondere, dass das Berechnungsresultat vom Zustand der Maschine unabhängig ist.
Also ein und dieselbe Anfrage an die gleiche Datenbasis gestellt wird,
immer wieder das gleiche Ergebnis liefern.
Egal was zwischendurch mit der Maschine passiert ist,
dass wir eben schon mal gesagt haben, hey, anfangen.
Ja, eine Veränderung. Natürlich. Ja, dann habe ich
die Welt verändert. Dann stelle ich in einer neuen Welt
eine alte Anfrage. Da muss ich mich nicht wundern,
dass die Antwort anders sein wird.
Was ist denn jetzt so ein Zuständig der Abstrakmaschine unabhängig sein?
So ist das nicht der Zustand der Abstrakmaschine. Nein,
die abstrakte Maschine kriegt eine Datenbank und eine Anfrage daran.
Das ist das, was sie, dass sie abstrakte Maschine bekommt.
Natürlich, wenn ich die, wenn ich die Datenbank ändere,
ändere ich meine Annahmen über die Welt.
Und dann muss ich mich nicht wundern,
dass die Schlussfolgerungen
daraus andere werden
Deshalb werden wir also in keinem der Beispiele, oder nein,
in ganz wenigen Fällen tatsächlich mit der Manipulation der Datenbank arbeiten.
Also auf dem ersten Zettel werden Aufgaben drin sein,
versuchen sie mal neue Fakten zur Datenbasis hinzuzufügen.
Das werden wir für praktische Zwecke nicht benutzen, denn das heißt,
nein, das ist eigentlich auch noch der unkritische Fall,
der kritische Fall ist das Entfernen von Fakten aus der Datenbank
und ersetzen durch neue Fakten.
Das ist nämlich eine Update-Operation und dann sind wir im immperativen Paradigma.
Das ist imperatives Programmieren, was sich schon angekündigt hat,
ich kann es machen in Prolog, kein Grundstück, ja.
Es ist nur, ich überschaue nach kurzer Zeit nicht mehr,
was dort doch eigentlich passiert.
Ja, weil ich nie so genau weiß,
was ist jetzt eigentlich der Zustand meiner Maschine und der Zustand
meiner Maschine ist dann eben genau der Inhalt der Datenbank.
Gut. Ja, wir wollen natürlich nicht nur Ja-Nein-Anfragen von einem System haben.
Wir wollen Berechnungsergebnisse sehen. Und um solche Berechnungsergebnisse zu bekommen,
in einer universellen Programmiersprache müssen wir irgendwelche Berechnungsvorschriften haben.
Also wir wollen Datenobjekte als Ergebnisse sehen. Bisher haben wir
nur bullsche Werte gesehen. Also ist etwas zur Relation
zugehörig oder nein. Und wir müssen das jetzt erweitern,
indem wir Variablen hinzufügen und Variablen, dienen uns als Behälter für Datenobjekte.
Also Datenobjekte werden an Variable gebunden oder an Bezeichner gebunden.
Und die Variablenbindung, die dort innerhalb der Berechnung hergestellt wird,
die wird dann als Berechnungsergebnis zur Verfügung gestellt.
Das ist genau die Idee. Also in dem Augenblick,
wo wir jetzt Anfragen mit Variablen zulassen, kriegen wir möglicherweise unterschiedliche Berechnungsergebnisse,
je nachdem, wie wir die Anfrage stellen.
Wir gucken uns im Augenblick nur an,
dass die Variablen in den Anfragen auftauchen.
Später werden wir auch die Variablen natürlich in der Datenbank verwenden.
Aber die Datenbank besteht bis jetzt immer noch aus Variablen,
freien Ausdrücken und zwar Grundstrukturen, die nur Atom enthalten.
Also, der Variablen Begriff ist hier etwas anders,
als er sonst in der Programmierung gesehen wird.
Es ist ein Paar und das Paar besteht immer aus
dem Bezeichner und dem Objekt,
was an dem Bezeichner gebunden ist
Die vaiablen Bindung ist dann eben genau eine solche Zuordnung eines
Datenobjektes an den vaiablen Bezeichner. Und dieser Prozess der vaiablen,
der Herstellung einer vaiablen Bindung wird Instanzierung genannt.
Und wir können deshalb also immer unterscheiden, für eine vaiable,
ist sie bereits instanziert oder ist sie noch uninstanziert.
Also bei einer nicht instanzierten vaiablen kenne ich die Identität des
Objektes noch nicht, was da dran gebunden wird.
Für eine instanzierte vaiable ist die Bindung
ein Objekt hergestellt
Instanziert, das hatte ich schon angedeutet, wird nur durch das Prolog-System.
Nicht durch den Programmierer. Es ist fast unmöglich, dies zu machen.
Natürlich geht es aber, es wird von Programmierer abgeschirmt.
Wir können die Bindung nicht aktiv herstellen und wir können eine Bindung auch nicht aktiv lösen.
Das ist das Privileg, das Programmiersystems. Ja,
wir müssen unsere Syntax nur geringfügig erweitern,
indem wir hier den vajablen Begriff noch hinzufügen.
Also wir haben jetzt einerseits konstant, andererseits Vajablen und
wir müssen etwas über die Gültigkeitsbereich von Vajablen sagen.
Alle die, die schon mal mit funktionaler Programmierung zu tun hatten,
wissen, das ist ein heißes Thema dort
Welche Variable ist eigentlich in welchem Bereich des Programms sichtbar?
Und hier haben wir ein ganz extrem enge Skopus-Regelung,
nämlich der Gültigkeitsbereich einer Variablen ist die Klausel.
Und eine Klausel wird immer durch einen Punkt abgeschlossen.
Das heißt, eine Variable ist nur innerhalb einer solchen Klausel gültig.
Das heißt, in unserem Fall bisher, entweder in der Datenbank,
in einem Fakt, den Fall betrachten wir noch nicht.
Oder aber in der Anfrage. Sie hat nur Bedeutung innerhalb dieser Anfrage,
nicht darüber hinaus. Und das sehen wir jetzt noch nicht,
aber das werden wir im Weiteren uns dann angucken können,
wir haben einerseits keine Referenz auf einen Speicherplatz.
Wir wissen nicht, wo das System seine Variablen hält.
Wir wissen nicht mal, wie es
die Variablen genannt hat
Der Preis, den wir für all dies bezahlen müssen,
ist nämlich, dass vajablen zur Konfliktvermeidung sofort umbenannt werden,
wenn sie ins System eingegeben werden.
Intern verwaltet das System andere Namen, als wir den Dingen gegeben haben.
Und da wir die internen Namen nicht wissen,
wir können sie natürlich erfahren, aber wir wissen sie im Normalfall nicht,
können wir auch nicht sagen, wo das überhaupt abgespeichert wird.
Das wird alles vom System verwaltet. Der Vorteil ist,
wir haben eben keine Referenz auf den Speicherplatz
und haben damit auch keinerlei Zustandsabhängigkeit
Und wir haben keinerlei Blockstruktur, weil dieser Skorpus derartig eng ist,
dass so etwas wie Verschattung von Variablen,
dass eine lokale Variable, eine globale Variable verschattet,
das ist ein zentrales Thema aller blockorientierten Sprachen,
das gibt es ja nicht.
Das heißt also, Skorpus-Fehler, dass man irrtümlicherweise angenommen hat,
ja, ich meine aber diese Variable da,
das kann hier auch gar nicht passieren,
weil der Skorpus eben extrem klein ist.
Der Witz an der Sache ist, dass Variablen nicht
instanziert sein müssen, wenn ich die Anfrage stelle.
Und da unterscheidet sich das Nein, unterscheidet sich nicht, sorry.
Hier kriegen wir erstmalig den Begriff der Unterspezifikation hinein.
Wir können also vollständig spezifizierte Teilziele eingeben. Das ist so
etwas, wie wir eben gemacht haben. Eine Grundstruktur,
die zu interpretieren ist, stimmt es, dass Susikal lebt.
Wir können jetzt aber Vajablen einbauen und kriegen dann partiell oder vollständig unterspezifizierte Ziele.
Also im ersten Fall bleibt uns die zweite Argumentstelle unterspezifiziert.
Dort steht eine Vajable, die unterschiedlich gebunden werden kann.
Je nachdem, was in der Datenbank vorgefunden wird.
Mit anderen Worten, diese Frage heißt, stimmt es,
dass Susi irgendetwas liebt?
Und wenn ja,
sage mir bitte was
Oder mit Kurzform. Was liebt Susi? Oder wen oder
was liebt Susi? Das hinten war ja wen oder was?
Oder wir können ein vollständig unterspezifiziertes Frage stellen und sagen,
wer liebt hier eigentlich wen oder was?
Und erwarten dann hier natürlich, dass wir alle Fakten aus
der Datenbasis aufgezählt bekommen. Das sind die drei Verwendungsweisen,
die wir auch wieder über die gesamte Vorlesung hinweg benutzen werden,
auch wenn die da drunter liegenden Programme komplizierter werden.
So, die Semantik ist immer,
prüfe die Konsistenz mit den Einträgungen der Datenbasis unter Unifikation
Und durch die Udifikation werden jetzt die Variablen instanziert oder wird die Variablenbindung hergestellt.
Und es ist eben das Ziel, eine oder gegebenenfalls auch
mehrere Variablenbindungen zu ermitteln, die die Aussage des Ziels wahrmachen.
Also finde eine Substitution für die Variable durch die Fakten der Datenbasis oder durch die konstanten,
die in der Datenbasis angegeben sind, in den Fakten.
Und damit wir dies machen können, hier also prüfen der
Konsistenz und der Unifikation, müssen wir den Unifikationsbegriff erweitern.
Das ist schon das erste Mal, dass wir das tun.
Unifikation zweit zum Zweiten. Wir unifizieren jetzt Variablen halt
ihr Strukturen
Ist eine Ermittlung einer vaiablen Substitution oder eben vaiablen Bindung,
die die Gleichheit oder Verträglichkeit der Strukturen herstellt.
Also einerseits die Anfrage, die hat Variablen drin und der Datenbasiseintracht,
der hat keine vaiablen, noch nicht später, wird er welche haben.
Und ich versuche, die Gleichheit herzustellen. Und
die Vergleich kriegt Vergleichskriterien, sind dann eben wieder Prädikatsnahme,
muss stimmen, Stelligkeit muss identisch sein.
Und jetzt unifiziere ich die Argumente. Und wie ich
die Argumente unifizieren muss, das steht hier unten drin.
Bis jetzt haben wir nur Konstanten und Variablen, also haben wir vier Fälle.
Eine Konstante trifft auf eine Konstante, dann ist es ein Identitätstest,
so wie wir das bisher auch hatten.
Ist Susi gleich Karl? Nein, die Unifikation scheitert.
Wenn eine Variable auf eine Konstante trifft, dann wird die vaiable instanziert,
dann wird die Variable auf den Wert Susi gebunden.
Und genau im umgekehrten Fall, wenn eine Konstante auf eine Variable trifft,
dann wird dieses Datenobjekt als Wert der Variablen gebunden.
Und der interessanteste Fall ist, was passiert, wenn zwei Variablen aufeinandertreffen?
Den haben wir jetzt noch nicht, aber das wird
ein durchgängiges Thema sein.
Dann wird zwischen diesen Variablen
sogenannte Co-Referenz hergestellt
Ab diesem Zeitpunkt, wenn die Unifikation zwei solche Variablen einmal aufeinandergelegt hat,
weiß ich, sie werden den gleichen Wert haben müssen.
Auch wenn ich den Wert selbst noch nicht kenne.
Ich kann also jetzt über einen Wert reden, obwohl ich ihn noch gar nicht kenne.
Und das ist das Grundprinzip der Unterspezifikation, dass ich über Dinge reden kann,
obwohl ich noch nicht weiß, wie sie genau ausbuchstabiert werden.
Also kann man sich Beispiele angucken,
liebt Susi oder wen oder was liebt Susi,
wird mit diesem Fakt unifiziert, Der Predikatsname ist identisch,
Stelligkeit ist in beiden Fällen zweistellig,
auf der ersten Argumentstelle trifft eine Konstante,
auf eine Konstante, die sind auch identisch.
Auf der zweiten Argumentstelle trifft eine Variable auf Buch und deshalb
wird die Variable auf den Wertbuch gesetzt und das ist das Berechnungsergebnis,
was ausgegeben wird. Im zweiten Falle liebt X ein Buch,
trifft auf die den Fakt. Susi liebt ein Buch,
Prädikatsnamen ist identisch, Stelligkeit ist identisch,
die Variable X wird an Susi gebunden und das wird als Ergebnis ausgegeben.
Aber da ist in der Datenbank noch ein zweiter Fakt,
der passt. Das heißt, jetzt kriege ich ein
alternatives Berechnungsergebnis durch die Unifikation dieser Anfrage mit diesem anderen Fakt
Und jetzt wird X an Karl gebunden auf der ersten Argumentposition
und als alternatives Berechnungsergebnis ausgegeben. Das heißt,
ich kriege jetzt hier tatsächlich eine Aufzählung aller Elemente in der Relation,
die diese Anfrage erfüllen.
In diesem Fall ist es einfach, da gibt es nur eine,
in diesem Fall muss ich zwei Ergebnisse bekommen.
Dennotationell heißt das, gibt es eine Substitution für die vajablen
Bezeichner in der Anfrage, sodass die Anfrage wahr wird.
Das ist die Denotationelle Semantik, die wir da zugrunde legen.
Operational heißt es wieder,
wir müssen in der Datenbank suchen,
ob wir
eine konstanzente vajablen Bindung finden
Also, wir können fragen, was liebt zu,
wen oder was liebt Susi,
wir nehmen den ersten Datenbank-Eintrag,
der scheitert auf der ersten Argumentstelle, die Unifikation,
der zweite ebenso, der dritte Datenbank-Eintrag ist erfolgreich,
Susi auf der ersten Argumentstelle und das X wird mit Buch unifiziert und gibt die vaiablen Bindung X gleich Buch und die gibt uns das System auf der Oberfläche aus.
Hier wieder im Suchraum,
die ersten beiden Fakten liefern kein Ergebnis,
da scheitert die Unifikation mit dem dritten Fakt,
ist sie erfolgreich und liefert ein Berechnungsergebnis
Interessant ist natürlich, wie komme ich an das zweite Ergebnis gegebenenfalls?
Und das passiert durch die Eingabe vom Semikolon am Prompt des Systems.
Also ich kriege ein Ergebnis ausgegeben oder sagen wir umgekehrt,
wir gucken uns das den zweiten Fall an.
Wer liebt Bücher? Ich kriege ein erstes Ergebnis mit der
vajablen Subsituation X ist Susi. Und wenn ich jetzt,
das ist interaktiv, jetzt ein Semikolon eingebe,
dann kriege ich, geht die Suche wieder von vorn los.
Das ist das sogenannte Backtracking, was angestoßen wird.
Was heißt Suche, ob es noch weitere Fakten in
der Datenbasis gibt, die die Anfrage ebenfalls wahrmachen.
Und dann kriege ich einen zweiten Fakt,
wo das klappt
Jetzt gibt es an dieser Stelle noch einen kleinen Unterschied
zu beachten, da dürfen sich nur nicht irritieren lassen.
In diesem Falle weiß das Prolog-System durch sein Indexierungssystem bereits,
dass es weitere Fakten nicht geben wird. Deshalb fragt es mich
gar nicht erst, ob ich weitere Fakten sehen wird,
denn die Frage wird immer negativ beantwortet werden.
Es gibt durchaus Umstände, wo nach dem Eingeben der Semikolons ein False kommt.
Dann heißt es, ich habe mein bestes versucht,
ich habe versucht, ein weiteres Ergebnis zu finden,
aber leider gab es keins.
Hier weiß das System schon von vornherein,
ein weiteres Ergebnis wird es nicht geben und deshalb gibt es
mir die Interaktionsmöglichkeit überhaupt nicht
Gut, in diesem Falle also, wenn ich das Semikolon eingebe,
dann ist die operationale Bedeutung dieser Aktion,
finde mir Alternative vaiablen Windung, indem du Backtracking betreibst oder zurücksetzen.
Und das heißt immer, bei einer erfolgreichen Unifikation,
also bei dem ersten Ergebnis, was ich hier habe,
Susi, wird ein Pointer in der Datenbank gesetzt.
Das macht das System für sie, das machen sie nicht.
Und bei Aufforderung, wenn jetzt also das Semikolon geht,
heißt das Gehe zurück bis zu dem letzten Zeiger,
den du gesetzt hast und mache dabei möglicherweise vaiablen Bindungen rückgängig,
die du schon hergestellt hast und dann setze an diesem Punkt
wieder die Suche fort
Und wir sehen also auch das Rückgängigmachen von Vayablenbindung oder die Update-Operation.
Macht nicht der Programmierer, das macht das System.
Und dieses Privileg lässt es sich nicht so einfach aus der
Hand nehmen. Also kann man das Ganze ja zusammenfassen.
Es gibt für ein gegebenes Prädikat oder ein Programm in Prolog
immer mehrere Eintritts- und Austrittspunkte.
Ich rufe das Ganze auf und es liefert mir ein erstes Ergebnis.
Wenn ich weitere Ergebnisse sehen will,
dann geht es über einen anderen Eintrittspunkt,
den sogenannten Redo-Eintrittspunkt,
wieder in die Prozedur hinein
Und liefert mir möglicherweise ein neues Ergebnis. Und wenn ich
wieder nach Alternativen frage, geht es wieder über diesen Redue-Eintritt
in die Prozedur hinein und liefert möglicherweise ein drittes Ergebnis.
Und erst wenn es keine weiteren Ergebnisse mehr gibt,
verlässt es und dann aber endgültig diese Berechnungsprozedur.
Also ich kann es einmal betreten und einmal verlassen,
aber ich kann beliebig viele Redues,
also wieder Eintritte in die Prozedur erzwingen,
indem ich eben fordere,
gib mir bitte
weitere Ergebnisse
Und das kann man jetzt fürs Tresen benutzen, indem man sogenannte Spypoints setzt.
Man kann entweder den Tres für ein ganzes Prädikat einschalten oder aber
man kann bestimmte Spypoints. Nämlich hier, sag mir nur,
wenn du das Prädikat das erste Mal betrittst und sag mir nur,
wenn du es zum letzten Mal verlässt,
da kann man sich den Treser genau einstellen,
da gucken sich bitte die Details dazu im Manual an.
So, damit sind wir beim Thema Testen eines Prädikats.
Das wird auch etwas sein,
was wir systematisch immer weiter ausbauen,
wo wir Schritt für Schritt
immer komplizierter machen
Muss man gucken. Okay. Hier ist es noch relativ
einfach. Das wird zunehmend komplizierter werden. Wir müssen,
wenn wir ein Prädikat testen wollen, in unserem Fall immer
positive und negative Beispiele testen. Es reicht nicht aus,
nur mit einem positiven Argument zu diskutieren,
die negativen Beispiele werden insbesondere dann bedeutend sein, um zu zeigen,
dass das Ganze auch unter diesen Umständen sicher terminiert.
Und, und das unterscheidet es jetzt von allen anderen Programmierparadigmen.
Es reicht nicht, positive und negative Beispiele hineinzustecken,
sondern ich muss alle Instanzierungsvarianten mal mir angeguckt haben
Möglicherweise verbirgt sich ein Programmfehler in einer Instanziierungsvariante, die ich nicht getestet habe.
Also insbesondere reicht es nicht aus, nur vollständig instanzierte Aufrufe
zu probieren. Ich muss auch partiell instanziierte Aufrufe austesten.
Und da wird deutlich, dass dies ein kombinatorisches Problem ist,
da ich für jede Argument stelle, ist instanziiert oder ist nicht instanziiert.
Und später auch noch ist unterspezifiziert, hinzukommt,
habe ich zumindest, wenn N die Anzahl der Argumente ist,
zwei Hoch-N-Testfälle, die ich generieren muss.
Das ist ein ernstes kombinatorisches Problem,
wo man sich sehr genau überlegen muss,
welche Testfälle probiere ich und welche habe ich damit,
dass ich diesen Testfall probiert habe,
gleich implizit mit erschlagen
Solange wir nur auf der Faktenebene sind und Grundstrukturen verwenden,
dann können wir so etwas natürlich machen, dass wir,
wie immer, in der Programmierung guter Stil ist,
dass wir die Testmenge bereits während der Programmerstellung sammeln und dann
in einem semi-automatischen Verfahren zum Testen verwenden.
Der zweite Aspekt ist, wir müssen im Prädikatschema angeben,
in welchen Instanziierungsvarianten wir ein Prädikat benutzen dürfen.
Und da gibt es eine Miklatur, die verwendet wird,
wesentlich sind für uns nur Das Plus, und das Fragezeichen,
das Plus heißt, diese Argumentstelle muss instanziiert sein.
Das ist ein Eingabeparameter. Und wenn diese Eingabe fehlt,
wird es keine korrektes Verhalten geben. Eingabeparameter ist all,
oder dieser, das ist der klassische Fall,
den sie bisher in der imperativen und in der funktionalen Programmierung kennengelernt haben.
Wenn sie dort ein Programm nicht mit den Eingabeparametern versorgen,
wird es nichts Vernünftiges tun.
Wir kriegen jetzt noch den Fall hinzu,
dass das Ding sowohl ein als auch Ausgabeparameter sein kann.
Also wir können dort instanziieren, dann ist es ein Eingabeparameter.
Oder wir können eine Variable hinschreiben,
dann ist es
ein Ausgabeparameter
Das machen wir durch ein Fragezeichen-Kennzeichen, kenntlich.
Und das Beispiel ist dann, also wir haben es gesehen,
unser Prädikat liebt, können wir in verschiedenen Instanzierungsvarianten aufrufen.
Das heißt also, sowohl das erste als auch das zweite Argument
ist ein- oder Ausgabeparameter und das ist genau das Verhalten,
was wir für relationale Datenbanken auch erwarten.
Werden wir die Datenbank aufschreiben, wollen wir nicht festlegen,
was ist Input, was ist Output,
sondern das soll erst zum Anfragezeitpunkt spezifiziert werden.
Gut, also jetzt haben wir hier mal ein Beispiel,
das hatten wir schon gesehen,
partiell instanzuiertes Eingabeziel und jetzt ein Dress und hier unten wieder
die Suchraum-Variante dazu
Wir fragen wen oder was liebt Susi? Die ersten beiden
Unifikationen mit Datenbank-Elementen scheitern. Die dritte Unifikation ist erfolgreich,
es wird eine Bindung hergestellt, die Bindung wird ausgegeben.
Und wenn ich nach Alternativen frage, nicht immer erlaubt ist mir,
dass das System tatsächlich zu machen, in diesem Fall dummerweise nicht,
dann wird die Suche wieder angestoßen.
Das, was ich hier darstelle, ist sozusagen ein idealisiertes Bild,
wie die Suche ablaufen müsste. Das System selber hat noch Shortcuts,
wo eine der Sachen gar nicht mehr sichtbar wären.
Also so müsste es ablaufen, nicht immer läuft es so ab.
Wenn ich nach der Alternative frage, dann wird zu dem letzten Entscheidungspunkt,
der dieses Ergebnis erzeugt hat, zurückgegangen,
und von dort aus die Suche fortgesetzt mit dem nächsten Datenbank-Eintrag,
da scheitert die Ulifikation aber auch und deshalb kommt kein weiteres Ergebnis dazu.
Das im Suchraum, die ersten beiden Fakten sind unbrauchbar,
der dritte Fakt liefert ein Ergebnis,
wenn ich das Weitersuchen erzwinge, guckt,
dass ich noch den vierten Fakt an und danach bricht die ganze Sache erfolglos ab.
Jetzt, das ist umgedreht,
die erste Argumentstelle ist unterspezifiziert und die zweite ist spezifiziert,
die ersten beiden Datenbankeinträumungen liefern wieder kein, keine erfolgreiche Unifikation,
weil auf der zweiten Argumentstelle das nicht unifizierbar ist.
Die dritte Datenbankeintragung liefert mir ein Ergebnis,
diese Variablenbindung wird ausgegeben und ich kriege die Frage, möchtest du weitere Ergebnisse sehen?
Dann wird zu diesem Entscheidungspunkt zurückgegangen mit dem nächsten Datenbankeintracht
die Suche fortgesetzt und das liefert mir das zweite Ergebnis,
die Suche nach einem dritten Ergebnis bringt nichts Neues hinzu.
Das Gleiche nochmal in der Sicht auf den Suchraum,
dritter Fakt und vierter Fakt liefern mir zwei alternative Variablenbindungen für
die Variable X
in meiner Anfrage
Ja, dies ist ein völlig unterspezifiziertes Ziel. Nein,
es ist ein partiell spezifiziertes Ziel. Obwohl hier nur Valiablen
drin auftauchen. Warum ist dieses Ziel nicht vollständig unterspezifiziert?
Wir hatten etwas über den Gültigkeitsbereich einer Variablen gesagt.
Und der Gültigkeitsbereich der Variablen ist die Klausel.
Die Klausel ist hier hinten zu Ende. Das heißt,
hier tritt zweimal die gleiche Variable auf. Dieses X,
was hier steht, ist gleich diesem X,
was hier steht.
Ich habe damit wieder
eine Co-Referenzforderung spezifiziert
Immer der Wert dieses X hier sein wird.
Es muss auch der Wert dieses X sein. Das heißt,
dies ist keine Forderung, die mir sagt,
gib mir die ganze Datenbank aus, sondern dies ist die Forderung,
gib mir die Elemente der Datenbank aus,
wo das erste Argument gleich dem zweiten Argument ist.
Oder mit genauer gesagt, wo das erste Argument mit dem zweiten unifiziert,
da wir bisher nur Konstanten haben, es sind beide Aussagen identisch.
So, und mit dieser Forderung, gib mir ein Datenbankelement oder ein Tupel aus,
was, wo die beiden Argumentbelegungen unifizierbar sind.
Mit dieser Forderung geht es in die Datenbanksuche und Mit dem ersten Fakt klappt es nicht,
weil ich dort zwei unterschiedliche Konstanten habe mit dem zweiten,
das gleiche Bild, mit dem dritten, das gleiche Bild,
mit dem vierten, das gleiche Bild und ich kriege ein False heraus.
Hier ist also die, die Ergebnismenge leer.
Ich habe eine Anfrage gestellt, die inkonsistent ist.
Es gibt in der Datenbank keinen einzelnen Eintrag,
der diese Anforderung erfüllt, die hier oben steht.
Wie gesagt, dies ist eine Anfrage,
die eine Bedingung an die Datenbank stellt.
Es ist nicht die Anfrage nach allen Elementen der Datenbank.
Die kommt gleich. Oder hier nochmal im Dress,
ja, ich habe vier Fakten,
die werden alle unifiziert mit diesem Ding
hier oben
Dadurch, dass hier Co-Referenz zwischen den beiden Argumentstellen herrscht,
gibt es keinen einzelnen Fakt, der die Bedingung erfüllt.
Und damit ist die Ergebnismenge leer. Es kommt also einfach
nur ein Forza zurück. Das, was wir also hier haben,
ist wieder ein Fall von Co-Referenz oder auch Variablen-Sharing genannt.
Durch mehrfache Verwendung einer Variablen innerhalb einer Klausel wird genau eine solche Chor-Referenz hergestellt und Co-Referenz kann ich immer dadurch herstellen,
dass ich entweder innerhalb der Klausel, und das haben wir eben gesehen,
das soll einfach ein A sein,
dass ich zweimal die gleiche Variable verwende oder dass ich so etwas mache,
Das ist jetzt ein kleiner Vorgriff, aber der Rest kommt gleich,
dass ich explizit fordere, X gleich Y. Wobei die Reihenfolge in diesem Falle vollkommen egal ist.
Ich könnte auch schreiben, sorry, ich könnte auch schreiben,
X soll koreferieren mit Y und dann möchte ich ein A von X A von Y haben.
Diese drei Anfragen werden semantisch vollständig äquivalent. Das heißt,
ich finde mir ein A, wo die beiden Argumentstellen gleich sind.
Das heißt, finden wir alle As, Für die,
die gleich die Unifizierbarkeit zusätzlich gefordert wird.
Und dies heißt, unifiziere erst diese beiden Bayablen-Stelle,
also Variablen-Sharing her oder Co-Referenz zwischen X und Y und suche
mir dann unter diesen Bedingungen alle Datenbankeintragungen.
Von der Ergebnismenge her sind sie vollständig identisch.
Vom Suchaufwand sind sie etwas unterschiedlich. Also Co-Referente Variablen
haben immer den gleichen Wert, sobald sie instanziert werden.
Aber ich weiß also schon, sie werden den gleichen Wert haben,
auch wenn ich den Wert selbst noch nicht kenne.
Also zum Aufrufszeitpunkt muss ich den Wert nicht kennen.
Ich weiß aber schon, es wird der gleiche Wert
sein müssen
Gut, und das ist schließlich der letzte Fall.
Jetzt habe ich zwei unabhängige Variablen, die nicht korreferent sind.
Und jetzt gehe ich in die Datenbank und ich kriege mit
dem ersten Fakt das erste Ergebnis mit dem zweiten Fakt,
das zweite Ergebnis mit dem dritten Fakt,
das dritte Ergebnis mit dem vierten Fakt,
das vierte Ergebnis oder hier nochmal im Suchraum dargestellt.
Das heißt, jetzt wird mir die Datenbank vollständig aufgezählt.
Ich habe beide Argumentstellen als Ausgabeparameter verwendet
Gut, damit sehen wir jetzt, dass wir verschiedenes Verhalten unseres Programms,
unsere komische Datenbank, ist tatsächlich ein einfaches Programm.
Wir können verschiedenes Verhalten dieses Programms auslösen, je nachdem,
wie wir die Anfrage stellen.
Also, wenn wir ein vollständig spezifiziertes Ziel eingeben,
dann wird auf Enthaltensein in der Relation getestet.
Das war also Anfrage, liebt Susi Hans. Wenn wir partiell unterspezifizieren,
dann kriegen wir eine Teilmenge der Relation aufgezählt,
nämlich all die Tupe, die die teilweise spezifizierten Forderungen der Anfrage erfüllen.
Und wenn wir das Ganze vollständig unterspezifiziert lassen,
kriegen wir die gesamte Relation
Aufgezählt. Die schöne Botschaft ist, auch,
nachdem wir die Variablen eingeführt haben, bleibt die Bezugstransparenz erhalten.
Das Ganze ist immer noch deklarativ, die denotationelle Semantik ist
die genau identisch mit der Operationalen zum Antik.
Gut, noch sind wir ja auch immer im Bereich der relationalen Datenbanken.
Dieses unterschiedliche Verhalten, was wir eben gesehen haben,
an diesen sehr einfachen Beispiel, können wir uns jetzt nochmal etwas aus Abstand betrachten.
Und wir kriegen damit eine andere Sicht auf Programmierung
Und diese andere Sicht auf Programmierung heißt Programmieren oder Programmabarbeitung als Anreicherung von Informationen.
Man kann den gesamten Berechnungsprozess, den Abarbeitungsprozess verstehen,
ich gebe eine nur teilweise spezifizierte Information in das System hinein.
Und das System fügt alles hinzu, was es zu dieser Anfrage weiß.
Alles, was dafür relevant ist, wird mit hineinunifiziert.
Das Ganze ist ein Unifikationsprozess, ein gigantischer.
Also wenn ich den Input vollständig spezifiziere,
ist mein Output
exakt dasselbe
Nur eben, also im Truhfalle immer, ja, im negativen Falle natürlich nicht.
Aber wenn das dann wahr ist, was ich hier frage,
dann kriege ich nicht mehr rein, als ich,
kriege ich nicht mehr raus, als ich reingesteckt habe.
Nur die Bestätigung, das, was ich reingesteckt habe,
ist wahr. Wenn ich partiell spezifiziere, wird eben das,
was dort noch offen ist, durch Informationen angereichert.
Also, ich kriege eine vaiablen Belegung für X,
die kann ich hier hinein in die Anfrage hinein substituieren und
ich kriege ein Dupel heraus, was diese Anfrage erfüllt.
Oder genau, umgekehrt,
in diesem Fall kriegen wir nur zwei Tupel heraus,
weil es eben zwei Möglichkeiten gibt,
X durch eine Konstante zu substituieren,
nämlich einmal Susi-Buch,
andererseits Kabelbuch
Ich kann hier auch ein partiell spezifiziertes Ziel angeben und ich kriege nichts hinzugefügt.
Es kommt ein leerer Ergebnismenge zurück. Das heißt,
dieser Anausdruck ist bereits so inkonsistent gegeben, die Datenbasis,
dass er sich nicht mit Informationen anreichern lässt.
Hier scheitert der Versuch, Informationen hinzuzufügen, die Forderung,
die hier gestellt wurde, ist bereits so stark,
dass es in der Datenbank keine Informationen gibt, die damit verträglich ist.
Deshalb ist die Menge leer. Und wenn ich hier alles unterspezifiziert lasse,
also beide Argumente stellen mit unabhängigen bariablen Versorge,
dann kriege ich eben für beide Variablen passende Substitutionen,
die ich hier einfügen kann und die mir im Endeffekt dazu führen,
dass ich die Relation vollständig aufzähle.
Also ich gehe immer mit einem, möglicherweise unterspezifizierten Ziel ins
Rennen und das Ziel wird so weit mit Informationen angereichert,
wie die Datenbank es zulässt.
In unserem Fall hier, wenn es ein Ergebnis gibt,
ist das Ergebnis immer vollständig spezifiziert.
Hier treten keine Variablen mehr auf. Wir werden im Weiteren dann
auch Programme sehen, wo auch in den Ergebnissen Variablen drinstehen.
Und dann wird es interessant. Das ist nämlich genau die Brücke,
die es uns erlaubt, über mit einem einzelnen Ausdruck
unendlich viele Berechnungsergebnisse zu spezifizieren.
Da wollen wir hin
So, also wir können eine Zwischenbilanz ziehen hier, die zwischen Bilanz heißt.
Wir legen im Programm im Verarbeitungssystem eine algorithmische Grundstruktur fest und
die algorithmische Grundstruktur haben wir in groben Zügen bereits kennengelernt.
Das ist nämlich Suche, die Datenbank von oben nach unten durch.
Bis du ein Eintracht findest, der mit der Anfrage unifizierbar ist,
substituiere gegebenenfalls Vayablen und gibt das Ergebnis aus.
Auf Aufforderung, geh ins Backtracking,
setze die Suche dort fort, wo du aufgehört hast,
Nicht ohne vorher vajablen Billionen rückgängig zu machen und suche nach weiteren Ergebnissen.
Das ist das allgemeine Programmschema, was immer abgearbeitet wird.
Und das wird sich auch nicht mehr ändern.
Das bleibt so bis zum Schluss. Und das führt dazu,
dass ein Programm nicht mehr einen Algorithmus beschreibt, sondern eine ganze Klasse von Algorithmen.
Und deshalb würde ich eher den Begriff des Algorithmen Schemas verwenden.
Wir sind also hier an der Stelle, wo einer der Grundbegriffe der Informatik,
nämlich der Begriff des Algorithmus, erweitert werden muss.
Der Begriff des Algorithmus sagt immer, was ist gegeben,
was ist gesucht? Wie komme ich vom Gegebenen zum Gesuchten
? Das ist genau das, was der Algorithmus spezifiziert.
Hier sage ich nicht mehr, was gegeben und was
gesucht ist
Ich schreibe nur noch auf, welcher Zusammenhang soll bestehen zwischen Input und Output.
Aber was Input ist und was Output ist,
das verrate ich jetzt zum Programmierzeitpunkt noch gar nicht.
Das wird zum Anfragezeitpunkt festgelegt. Ja,
also erst in der Anfrage beim Prädikatsaufruf,
da werden die Details spezifiziert und insbesondere die Verarbeitungsrichtung steckt nicht im Programm drin,
im generellen Fall nicht, sondern im Prädikatsaufruf.
Und das ist eine Eigenschaft, diese Richtungsunabhängigkeit,
das ist ein wesentliches Merkmal der relationalen Programmierung,
was man in anderen Paradigmen in der Form nicht findet.
Und in Abhängigkeit vom Prädikatsaufruf kriege ich ganz unterschiedliche prozedurale Abläufe
und auch unterschiedliche Ergebnisse dann
Gut, damit wir sinnvolle Sachen machen können. Ich habe
vorhin schon praktisch angedeutet hier, es bleibt nicht bei solchen einfachen Zielen.
Wir können auch komplexe Teilziele eingeben. Und die Frage ist,
was sind die Operatoren, die wir brauchen,
um aus einfachen Teilzielen komplexe Ziele zu bilden.
Und der Wichtigste, den wir brauchen,
ist die Konjunktion, das ist nämlich dieses Komma hier.
Das ist das Logische und. Also denotationelle Semantik dieses Kommas
hier ist das Logische und und die operationale Semantik heißt,
arbeitet diese Teilziele, die jetzt hier drin stehen,
liebt x Susi und liebt x Geld sequenziell ab.
Zuerst das erste, dann das zweite
Aber natürlich unter Variablensubstitutionen. Wenn ich hier die Variable X verwende und hier
die Variable X verwende, dann ist das innerhalb der gleichen Klausel.
Die Klausel ist das Tier hinten zu Ende. Das heißt,
es gilt Co-Referenz zwischen diesem X und diesem X. Soll heißen,
gibt es eine Person, die Susi mag und eine Person,
die auch Geld mag.
Das ist diese Anfrage ins Deutsche übersetzt.
Und tatsächlich enthält die Datenbank eine Person und das ist Hans.
Und das Ganze ist eben nur erfolgreich,
wenn ich alle Teilziele erfolgreich abarbeiten konnte
Beachten der Chorreferenzbeziehungen, die hier drin stehen.
Das ist nichts anderes als ein Self-Join, den man in
der relationalen Datenbankwelt kennt. Ich habe eine Tabelle,
lieb Tabelle und ich verbinde unterschiedliche Tupe in dem,
in der Tabelle, in dem ich eben hier eine Forderung
der Erstargumentstelle hier gleich der ersten Argumentstelle hier fordere.
Ein Equi-Join heißt es in der Datenbankwelt. Also wir erweitern die Syntax damit,
dass wir außerelementaren Zielen auch komplexe Ziele erlauben und komplexe Ziele
bilde ich eben, indem ich elementare Ziele durchkomme,
von einem weiteren Ziel trenne, was rekrusiv ist.
Damit heißt, ich kann beliebig viele solche Teilziele in
eine Klausel schreiben
Wo es eine Konjunktion gibt, muss es auch eine Dissunktion geben,
die Dyssunktionen, die haben wir bereits interaktiv gesehen.
Das ist nämlich das Sigmy Colon, gib mir alternative Berechnungsergebnisse.
Hier heißt es, das sind zwei alternative Anfragen.
Entweder soll X-Susi lieben oder aber Susi soll X lieben.
Und ja, die Denotationelle Semantik ist dann das Logische.
Oder operational heißt es,
ich werde dies wieder sequenziell abarbeiten und die Dissunktion ist erfolgreich,
wenn wenigstens eines der beiden Teilziele
erfolgreich abgearbeitet worden ist
Die Pragmatik, die ich dazu sagen will, ist,
vermeiden Sie diese Desjunktionen.
Sie ist manchmal hilfreich, um einfachere Anfragen zu schreiben.
Sie ist aber ganz problematisch, wenn die Ausdrücke komplexer werden,
noch zu verstehen, was wirklich passiert.
Gleich kann sich jemand vorstellen, warum, worin das Problem besteht?
Wenn wir gleich das X-Tage, die 11.000 sind.
Eben nicht mehr. Das ist die einzige Ausnahme zu der
Regel der Gültigkeitsbereich der Vagablen, ist die Klausel,
das X und das X,
hier sind jetzt
verschiedene Xe
Ja. Und das sauber voneinander zu trennen,
dass mal der Gültigkeitsbereich der Klausel ist, der Variablen ist die Klausel,
dass es mal gilt im Falle der Konjunktion auf der vorhergehenden
Folie und hier im Falle der DSU-Uktion gilt es nicht mehr.
Das ist verwirrend. Ja, und deshalb also meine Warnung, vermeiden Sie es,
Sie können viel Ärger vermeiden auf die Art und Weise, ja.
Jetzt lese ich da, dass es ein komplexes Ziel ist,
dass ein elementares Ziel wieder ein Ziel bestehen kann,
da kann eine Konjunktion bereit sein.
Ja. Manchmal kommt man ja auf die Situation,
dass man das schachteln möchte, kann man da klammern.
Ja, unbedingt, ja, ja,
klammern geht immer,
Ja, natürlich gibt es auch hier die Präferenz.
Konjunktionen bindet stärker als Disjunktionen. Es gibt Operatorpräzedentenzen,
da kommen wir noch drauf, wie die gesetzt sind.
Und wenn ich die außer Kraft setzen will, kann ich klammern.
Ich kann natürlich klammern, immer drumherum setzen.
Helfen Sie nicht, so schaden Sie auch nicht.
Das ist um die Verhältnisse eindeutig zu machen,
empfiehlt sich in solchen Fällen auch immer Klammern.
Die Antwort ist aber, wir werden es in den Anfragen
so gut wie nicht brauchen.
Und in Prädikatsdefinitionen werden wir es schon gar nicht brauchen,
weil diese Disjunktionen in einer Prädikatsdefinition immer durch zwei alternative Klauseln ersetzt werden kann.
Es ist semantisch vollkommen äquivalent. Das heißt,
es gibt auch gar keinen Grund,
das Emikolon zu benutzen
Man kommt ohne aus, es ist nur der Vollständigkeit halber mit zur Verfügung gestellt.
Dass die Uhr dahinten nicht geht, ist ein ganz schlechter Service.
Aber nein, ich habe noch einen binzigen Augenblick. Gut,
wenn ich konjunktiv verknüpfte Anfragen habe, dann muss ich mich natürlich darüber unterhalten,
wie die Suche in diesem Fall ablaufen soll.
Und da gilt die Vereinfachung, Vereinbarung, wir machen eine tiefe Suche im Prolog.
Wenn ich mit einem Teilziel eine Eine erfolgreiche Variablenbindung hergestellt habe,
dann wird diese Variablenbindung an den auf verbleibende Teilszenen überprüft.
Das heißt, ich muss erst ein Teilziel vollständig abarbeiten,
ehe ich zur Bearbeitung des nächsten Teilsziels übergebe.
Und das heißt, im Begriffen des Suchraumes, dass wir
den Suchbaum in die Tiefe expandieren, daher der Name Tiefensuche.
Und das Backtracking heißt dann, ich muss wieder Entscheidungspunkte setzen,
wann immer ich ein Teilziel erfolgreich instanziert habe,
setze ich einen Entscheidungspunkt, zu dem ich im Ernstfalle wieder zurückgehen kann.
Und die Entscheidungspunkte, das muss nicht einer sein,
das können mehrere sein, werden in umgekehrter Reihenfolge wieder aktiviert.
Hört sich komplizierter an, als es ist
Hier ist eine Anfrage, eine Konjunktivverknüpfte Anfrage. Wer liebt zu?
Sie, wer liebt Geld? Wir hatten schon vorhin gesehen,
Hans ist der einzige, die einzige Instanz. Also,
ich gehe jetzt in die Suche hinein mit dem ersten Teilziel.
Ich kriege hier mit dem ersten Fakt kein Ergebnis,
weil Susi mit Geld nicht unifiziert.
Ich kriege mit dem zweiten Fakt ein Ergebnis,
wodurch Hans an X gebunden wird.
Das ist eine Wertebindung. Und mit dieser Wertebindung,
jetzt habe ich ein Ergebnis. Mit dieser Wertebindung gehe ich
in das zweite Teilziel,
das dann lautet,
liebt Hans Geld
Neue, zweite Teilziel oder jetzt instanziert,
der zweite Teilziel, muss ich an der Datenbank überprüfen.
Und da gibt es einen Antrag dazu, der liefert mir ein Ergebnis.
Das heißt, als erstes Ergebnis kriege ich heraus,
jawohl, Hans erfüllt die Bedingungen dieser Anfrage.
Wenn ich jetzt nach Alternativen suche, dann muss ich eben
in einen Entscheidungspunkt zurückgehen, in dem ich gerade war.
Das ist nämlich hier an dieser Stelle.
Ich bin an dieser Stelle und habe einen Fakt bereits angeguckt.
Die restlichen habe ich mir noch nicht betrachtet. Also muss
ich an der Stelle weitermachen. Nur das unifiziert mich auf
der zweiten Argumentstelle, das udifiziert nicht auf der ersten.
Das unifiziert auch nicht auf der ersten. Das heißt,
dieses Teilziel habe ich jetzt erfolglos weiter abgearbeitet. Ich habe
dadurch keine zusätzliche Vayablenbindung bekommen. Dann muss ich ins Backtracking gehen,
dass ich den Entscheidungspunkt weiter oben gehe und jetzt sage,
okay, bis hierhin habe ich die Datenbank abgearbeitet,
aber da sind ja noch zwei Fakten.
Die muss ich mir auch angucken. Nur dummerweise liefert keiner
dieser beiden Fakten eine erfolgreiche Unifikation mit diesem ersten Teilziel.
Das heißt, ich kriege bereits für das erste Teilziel
keine weitere Vayablenbindung und deshalb kommt auch kein weiteres Ergebnis zustande
Im Tres markiere ich dies immer so, dass ich sage,
ich habe hier eine Anfrage und setze einen Pointer in die Datenbank.
Dann fange ich an zu suchen. Ich kriege mit der zweiten Eintragung
eine variablen Bindung. Mit dieser variablen Bindung muss ich jetzt
in das zweite Teilziel hineingehen und fragen, liebt Hans Geld?
Und ich kriege, und das ist ein zweiter Entscheidungspunkt,
den ich setze, ich kann das positiv beantworten.
Und wenn ich jetzt nach Alternativen frage,
heißt es, gehe zu diesem Entscheidungspunkt zurück
Und arbeite dort die restlichen Datenbankeintragungen aus. Und das sind eben
noch diese beiden. Diese beiden liefern kein weiteres Ergebnis,
diese drei. Warum ist denn da ein Zeilenvorschub drin?
Okay, also diese drei liefern ein Ergebnis, liefern kein Ergebnis.
Deshalb komme ich wieder ins Backtracking und muss jetzt zum Entscheidungspunkt
Nummer eins zurückgehen. Und der Entscheidungspunkt Nummer eins,
da hatte ich bereits zwei Fakten abgearbeitet.
Ich muss jetzt noch diese beiden Fakten anfassen,
die liefern aber auch kein Ergebnis,
sodass insgesamt
ein False rauskommt
Also, jetzt muss ich den Nicht-Teterminismus tatsächlich durcharbeiten. Das Ziel wird natürlich sein,
dass wir diese algorithmischen Abläufe im Endeffekt gar nicht mehr brauchen,
dass wir uns darauf verlassen können,
dass das System das im Hintergrund schon richtig machen wird,
dass wir nur im Notfalle tatsächlich auf diese Drehsebene zurückfallen.
Dass wir wirklich versuchen, deklarative Programme zu schreiben,
verstehen, wann die Deklarativität funktioniert,
wo die Fallstricke sind und dass wir es gar nicht mehr nötig haben,
tatsächlich in jedem Falle auf dieser Trace-Ebene uns zu begeben,
weil das Ziel ja genau ist,
diese algorithmischen Abläufe,
dass man sich um die
nicht mehr
kümmern muss
Okay, ja, damit ist meine Zeit dann auch abgelaufen.
Die anonyme Bayable brauchen wir nicht unbedingt. Das ist eine
kleine Erweiterung noch, die sowieso nur beschränkten oder eigentlich eher für
den Nutzer von Interesse ist, als für das System.
Das können wir uns auch nächsten Freitag anschauen und bis dahin
wünsche ich Ihnen ein schönes Wochenende