Neue Version meines Prolog-Tuts
Transcription
Neue Version meines Prolog-Tuts
Eine Einführung in Prolog Emanuel Kitzelmann Professur für Angewandte Informatik / Kognitive Systems Fakultät für Wirtschaftsinformatik und Angewandte Informatik Otto-Friedrich-Universität Bamberg D-96045 Bamberg 10. November 2004 [email protected], http://www.cogsys.wiai.uni-bamberg.de Inhaltsverzeichnis 1 Vorwort 3 2 Grundlagen 2.1 Prolog und Horn-Klauseln . . . . . . . . 2.1.1 Horn-Klauseln . . . . . . . . . . 2.1.2 Fakten, Regeln, Anfragen . . . . 2.1.3 Prolog-Syntax . . . . . . . . . . 2.1.4 Beispiel . . . . . . . . . . . . . . 2.1.5 Kalküle – Sinnfreies :-) Beweisen . . . . . . . . . . . . 3 Weitergehende Konzepte 3.1 Rekursive Regeln . . . . . . . . . . . . . . . 3.2 Listen . . . . . . . . . . . . . . . . . . . . . 3.2.1 Wichtige Prädikate auf Listen . . . . 3.2.2 Beispielanfragen . . . . . . . . . . . 3.2.3 Implementationen für last und append 3.2.4 Listen sortieren . . . . . . . . . . . . 3.3 Arithmetik . . . . . . . . . . . . . . . . . . . 3.4 Der Cut . . . . . . . . . . . . . . . . . . . . 3.5 Negation . . . . . . . . . . . . . . . . . . . . 3.6 bagof, setof, findall . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 5 6 6 12 . . . . . . . . . . 13 13 14 15 16 17 18 19 20 20 20 1 Vorwort Diese Einführung entsteht im Rahmen der Vorlesung Kognitive Agenten I: Intelligente Agenten. Intention ist es, die Übung, in der Prolog eingeführt wird, zu unterstützen. Zum jetzigen Zeitpunkt ist diese Einführung im Entstehen und wird kontinuierlich erweitert werden. 3 2 Grundlagen 2.1 Prolog und Horn-Klauseln Prolog-Programme bestehen im Wesentlichen aus einer bestimmten Form prädikatenlogischer Formeln, sogennanten Horn-Klauseln, und werden Datenbasen genannt. Die Benutzung einer Datenbasis erfolgt über Anfragen, die wiederum Formeln sind und von Prolog gegeben der Datenbasis zu beweisen versucht werden. Anfragen können (implizit existenzquantifizierte) Variable enthalten. Falls eine solche Anfrage von Prolog bewiesen werden kann, liefert Prolog die gültigen Belegungen als Ergebnis. 2.1.1 Horn-Klauseln Ein Prädikat ist eine Formel, die mittels eines Relationssymbols gebildet ist, beispielsweise oder in Präfix-Schreibweise . Anderes Beispiel: vater darth_vader luke . Ein Literal ist ein Prädikat (positives Literal) oder ein negiertes Prädikat (negatives Literal). Beispie le: , vater darth_vader luke (positive Literale), , vater darth_vader luke (negative Literale). Eine Disjunktion von Formeln ist eine Formel, in der diese (Teil-)Formeln mittels des Disjunktors verbunden sind: Eine Horn-Klausel ist eine Disjunktion von Literalen, wobei maximal eines der Literale positiv ist: (2.1) Eine Horn-Klausel heißt definit, wenn sie (genau) ein positives Literal besitzt. Gerechtfertigt durch die Äquivalenzen ! die für beliebige Formeln ) ben als * " $&% " '(% % # (2.2) (2.3) gelten, läßt sich die obige Horn-Klausel auch schrei )+ ## # 4 # (2.4) 2.1.2 Fakten, Regeln, Anfragen Prolog differenziert begrifflich zwischen zwei Arten definiter Horn-Klauseln: Fakten sind definite Horn-Klauseln, die aus genau einem positiven Literal, also aus genau einem Prädikat beste hen: (2.5) Regeln sind definite Horn-Klauseln, die auch aus negativen Literalen bestehen, die also eine Disjunktion aus einem positiven und mindestens einem negativen Literal sind. Wie oben gezeigt, läßt sich eine solche Regel auch darstellen als Implikation von einer Konjunktion positiver Literale zu einem einzelnen positiven Literal: wird Kopf der Regel, aus einem Kopf. # + # # (2.6) # Körper der Regel genannt. Ein Fakt besteht einfach nur Ein Prolog-Programm ist eine Menge von Fakten und Regeln und wird auch Datenbasis genannt. In den Fakten und Regeln können natürlich Variable vorkommen. Diese sind implizit allquaniti fiziert, d.h. falls in Fakten oder Regeln beispielsweise die Variable vorkommen, sind diese gilt, dass ...”. sinngemäß zu lesen als: “Für alle An ein Prolog-Programm – eine Datenbasis – kann man Anfragen stellen. Eine Anfrage ist eine Konjunktion positiver Literale: # # # (2.7) Die Variable in einer Anfrage sind implizit existenzquantifiziert. Eine Anfrage, falls in ihr bei spielsweise die Variable vorkommen, ist zu lesen als:“Existieren ein und ein , so dass ...?”. Prolog versucht nun, die Anfrageformel aus den Formeln der Datenbasis zu beweisen. Im Falle einer Anfrage mit Variablen muss Prolog dazu mindestens eine Belegung der Variable finden, so dass die mit dieser Belegung instanziierte Anfragekonjunktion bewiesen werde kann. Eine Belegung ist eine Funktion, die Variablen Terme (z.B. Konstante) zuweist und die Instanziierung einer Formel (ohne Quantoren) mit einer Belegung ist eine neue Formel, die aus der alten hervorgeht, indem in ihr alle Variable gemäß ihrer Belegung ersetzt sind. Für das Ergebnis einer Anfrage sind drei Fälle zu unterscheiden: 1. Die Anfrageformel ist beweisbar und enthält Variable: Dann ist das Ergebnis die gefunde Belegung bzw. beliebig viele der möglichen Belegungen. 2. Die Anfrageformel ist beweisbar, enthält aber keine Variable: Dann ist das Ergebnis schlicht yes. 3. Die Anfrageformel ist nicht beweisbar: Dann ist das Ergebnis No. Beispiele folgen weiter unten. 5 2.1.3 Prolog-Syntax Für die Junktoren hat Prolog eine spezielle Syntax: Der Konjunktor, in der Regel mit dem Symbol # repräsentiert, wird in Prolog durch ein Komma ’,’ ausgedrückt, der Disjunktor durch ein Semikolon ’;’ und der (umgekehrte) Subjunktor + durch einen Doppelpunkt gefolgt von einem Minuszeichen ’:-’. Ein Fakt bzw. eine Regel kann auf mehrere Zeilen verteilt werden. Fakten und Regeln müssen jeweils mit einem Punkt abgeschlossen werden. Der Fakt in Formel (2.5) würde in Prolog also folgendermaßen geschrieben: p. Die Regel aus Formel 2.6 folgendermaßen: p_1 :- p_2, ..., p_n. An der Groß- bzw. Kleinschreibung unterscheidet Prolog Variable von allen anderen Symbolen. Variable werden groß geschrieben, Relationssymbole (mit denen Prädikate gebildet werden), Operationssymbole (mit denen Terme gebildet werden) und Konstante (nullstellige Operationssymbole, die Individuen bezeichnen) werden klein geschrieben. Kommentare können in den Programmcode durch ein Prozentzeichen eingefügt werden. Alles, was in einer Zeile auf ein % folgt, wird vom Prolog-Interpreter ignoriert. 2.1.4 Beispiel Beispielprogramm % Etwas Starwars-Verwandschaft % Fakten vater(darth_vader, luke). vater(darth_vader, leia). mutter(shmi, darth_vader). mutter(leia, jacen). mutter(leia, jaina). % Regeln elternt(X,Y) :- vater(X,Y). elternt(X,Y) :- mutter(X,Y). 6 Dieses Programm besteht aus einigen Fakten zu einem wichtigen Teil der Starwars-Verwandschaft und zwei Regeln, die definieren, wann jemand ein Elternteil von jemand anderem ist. vater, mutter und elternt sind Relationssymbole, darth_vader, luke, leia, jacen und jaina sind Konstante, bezeichnen also Individuen, und X und Y sind Variable. Variable und Konstante sind elementare (einfache) Terme. vater(darth_vader, luke) oder elternt(X,Y) sind Beispiele für Prädikate, also bestimmte prädikatenlogische Formeln, nämlich die, die mittels eines Relationssymbols und Termen als Parameter gebildet sind. Was mit vater(darth_vader, luke) gemeint ist, dürfte klar sein – natürlich das Faktum, dass Darth Vader der Vater von Luke ist. (Prolog weiß natürlich von dieser Bedeutung nichts.) Die beiden (implizit allquantifizierten) Regeln lassen sich folgendermaßen interpretieren: “Für alle X,Y gilt, dass X ein Elternteil von Y ist, wenn X Vater von Y ist” und “Für alle X,Y gilt, dass X ein Elternteil von Y ist, wenn X Mutter von Y ist”. Eine einfache Anfrage Eine Anfrage sieht nun beispielsweise folgendermaßen aus: ?- elternt(darth_vader, leia). Auch eine Anfrage endet mit einem Punkt und wird nach einem RETURN vom Prolog-Interpreter bearbeitet. Das ?- ist nicht Teil der Anfrage, sondern das Symbol des Promts des Interpreters. Die Bedeutung der Formel in ihrer Rolle als Anfrage ist: Ist Darth Vader ein Elternteil von Leia? Um die Anfrage zu beantworten, versucht Prolog nun, die Formel, also die Aussage “Darth Vader ist ein Elternteil von Leia”, zu beweisen. Die Antwort von Prolog lautet: Yes ?Prolog hat unsere Beispielanfrageformel unter Voraussetzung der Datenbasis bewiesen bzw. die Anfrageformel aus der Datenbasis gefolgert. Das Ergebnis – unsere spezielle Bedeutung der Formeln (von denen Prolog allerdings nichts weiß) vorausgesetzt –, nämlich dass Darth Vader ein Elternteil von Leia ist, ist ja auch richtig. Obwohl Prolog von dem, was uns als Programmierer die Formeln bedeuten, überhaupt nichts weiß, ist es Prolog möglich, auf Fragen von uns, die für uns wiederum eine Bedeutung haben, von denen Prolog nichts weiß, Antworten zu finden, die, von uns mittels unserer Bedeutung interpretiert, stimmen. Nochmal an diesem Beispiel verdeutlicht: Prolog weiß weder, was uns als Programmierer die Formeln der Datenbasis bedeuten, noch was uns die Anfrageformel elternt(darth_vader, leia) bedeutet. Trotzdem kommt Prolog zu dem Ergebnis yes, das für uns bedeutet, dass Darth Vader ein Elternteil von Leia ist. Und diese Antwort stimmt (obwohl dieser Fakt nicht explizit in der Datenbasis kodiert ist). Die kurze Erklärung der Frage, wie das zuverlässig funktionieren kann, steht in Abschnitt 2.1.5. 7 Jetzt aber nochmal an Hand unserer Bedeutung der Formeln erläutert, warum es plausibel ist, aus den Formeln der Datenbasis zu folgern, dass Darth Vader ein Elternteil von Leia ist: In gilt, dass Individuum der Datenbasis besagt die erste Regel ja, daß für alle Individuen Elternteil von Individuum ist, wenn Individuum Vater von Individuum ist. Insbesondere gilt also, dass Darth Vader Elternteil von Leia ist, wenn er ihr Vater ist. Dass er ihr Vater ist, besagt aber gerade der zweite Fakt der Datenbasis. Die Folgerung ist also plausibel. Prologs Bearbeitung der Anfrage Wie bewerkstelligt Prolog diese Folgerung? Im Allgemeinen wählt Prolog sich als erstes das erste Prädikat der Anfragekonjunktion aus. In unserem Fall besteht die Anfrage nur aus dem einzigen Prädikat elternt(darth_vader, leia), also wird es ausgewählt. Nun sucht Prolog sich die erste/oberste Horn-Klausel in der Datenbasis, deren Kopf mit dieser Anfrage unifizierbar ist. Zwei Horn-Klauseln sind unifizierbar, wenn eine Belegung ihrer jeweiligen Variablen existiert, so dass die Formel, die aus der einen Formel durch die Instanziierung ihrer Variable mit ihrer Belegung hervorgeht, dieselbe ist, wie diejenige Formel, die durch Instanziierung der Variable der anderen Formel mit ihrer Belegung hervorgeht. Im Falle einer Anfragebearbeitung von Prolog werden offensichtlich Prädikate – nämlich Anfrageprädikate mit den Köpfen (die ja Prädikate sind) von Datenbasis-Formeln – unifiziert. Die erste Horn-Klausel, deren Kopf mit dem Anfrageprädikat unifizierbar ist, ist elternt(X,Y) :- vater(X,Y). Der Kopf dieser Regel ist elternt(X,Y). Das Anfrageprädikat ist elternt(darth_vader, leia). Lediglich in dem einen Prädikat kommen Variable vor. Wenn wir nun die Belegung wählen, die auf darth_vader und auf leia abbildet, dann ergibt die Instanziierung von elternt(X,Y) mit dieser Belegung die Formel elternt(darth_vader, leia), stimmt also mit dem Anfrageprädikat überein. Also sind die beiden Prädikate unifizierbar. Als Ergebnis dieser Uni fikation sind jetzt die Variable gebunden an die Terme, die ihnen die Belegung zuordnet. Diese Bindung wird jetzt auf die ganze Regel übertragen, die mit dieser Bindung instanziiert wird und nun lautet: elternt(darth_vader, leia) :- vater(darth_vader, leia). Ob elternt(darth_vader, leia) beweisbar ist, hängt nun (nur noch) davon ab, ob vater(darth_vader, leia) beweisbar ist. Andersrum: Um elternt(darth_vader, leia) zu beweisen, genügt es, vater(darth_vader, leia) zu beweisen. Das Original-Anfrageprädikat wird also gestrichen und stattdessen ein neues, nämlich vater(darth_vader, leia) zur Anfrage hinzugefügt. Genau das tut Prolog. Es generiert während der Bearbeitung einer Benutzer-Anfrage intern neue Anfragen. Die neue Anfrage vater(darth_vader, leia) wird nun genauso bearbeitet, wie die ursprüngliche: Es wird nach der ersten Horn-Klausel in der Datenbasis gesucht, deren Kopf mit dem ersten Anfrageprädikat unifizierbar ist. Das Ergebnis dieser Suche ist der Fakt vater(darth_vader, leia), der ja, wie es alle Fakten tun, nur aus einem Kopf besteht. Dieser Kopf ist nun schon genau dasselbe Prädikat wie das von Prolog generierte Anfrageprädikat. Es ist also trivialerweise mit diesem Fakt unifizierbar. Da diese gefundene Horn-Klausel ein Fakt ist und von gar keiner weiteren Voraussetzung abhängt, ist das intern generierte Anfrageprädikat vater(darth_vader, leia) bewiesen, einfach dadurch, dass 8 es als Fakt in der Datenbasis vorkommt. 1 Da die aktuelle (intern generierte) Anfrage lediglich aus diesem einen Prädikat besteht, ist gleichzeitig die ganze intern generierte Anfrage bewiesen. Damit ist also die Voraussetzung/der Körper der Formel elternt(darth_vader, leia) :- vater(darth_vader, leia) bewiesen und damit, wie oben schon erläutert, auch elternt(darth_vader, leia), die ursprüngliche Anfrage. Prologs Bearbeitung von Anfragen allgemein Um eine Anfragekonjunktion zu beweisen, wählt Prolog das erste Prädikat der Konjunktion aus und durchsucht die Datenbasis von oben nach unten nach einem Fakt oder einer Regel (allgemein nach einer Horn-Klausel), mit dessen/deren Kopf (wobei ein Fakt lediglich aus seinem Kopf besteht) sich das gewählte Anfrageprädikat unifizieren lässt. Durch die Unifikation kommt eine Bindung der Variable des Anfrageprädikats und der Horn-Klausel zustande. Das Anfrageprädikat wird nun aus der Anfragekonjunktion gelöscht und stattdessen der Körper der Horn-Klausel, instanziiert gemäß der Belegung ihrer Variablen, der Anfragekonjunktion anstelle des gelöschten Prädikats hinzugefügt. Falls die Horn-Klausel eine Fakt ist, wird natürlich einfach nur das Anfrageprädikat gelöscht und nichts hinzugefügt, denn ein Fakt besitzt ja keinen Körper. Ziel ist es, eine leere Anfragekonjunktion zu erreichen, da in diesem Fall alle Prädikate bewiesen wurden. Falls es nicht gelingt, ein Prädikat mit einer Horn-Klausel zu unifizieren, macht Prolog die letzte Unifikation rückgängig und sucht eine alternative Horn-Klausel (die nächste passende Horn-Klausel in der Datenbasis) zur Unifikation. Falls sich die Anfrageliste endgültig nicht leeren lässt, weil es keine Suchschritte mehr rückgängig zu machen gibt, antwortet Prolog no, da in diesem Fall die Anfrage nicht bewiesen werden konnte. Man kann sich eine Abarbeitung als Tabelle aufschreiben, wobei man in die erste Zeile links die Anfrageliste und rechts die gewählte Horn-Klausel aus der Datenbasis schreibt. In die zweite Zeile schreibt man die Belegung der Variablen in der Form X/Term, Y/Term_2 usw.. In die dritte Zeile dann wieder links die neue Anfrageliste, rechts die gewählte Horn-Klausel usw.. Um in der letzten Zeile bei erfolgreicher Abarbeitung die leere Anfrageliste zu markieren, benutzen wir das Symbol . Um im Falle des Backtracks den Rücksprungpunkt anzugeben, wird jede zweite Zeile numeriert. Falls keine Horn-Klausel zur Unifikation gefunden werden kann, notieren wir das durch das Symbol –. Die Antworten von Prolog sind fett geschrieben. Die Tabelle für die Beispielanfrage von oben sähe dann folgendermaßen aus: 1. 2. elternt(darth_vader, leia) elternt(X,Y) :- vater(X,Y) X/darth_vader, Y/leia vater(darth_vader, leia) vater(darth_vader, leia) keine Bindung, da keine Variable 3. Yes 1 Es gilt ganz grundsätzlich und trivialerweise, daß eine Formel aus einer Menge von Formeln gefolgert werden kann, wenn sie selbst schon in dieser Menge enthalten ist. 9 Eine nicht-beweisbare Anfrage Als nächstes stellen wir die Anfrage ?- elternt(luke, jacen). die Prolog korrekterweise mit No ?beantwortet. Warum? Die erste Horn-Klausel, mit deren Kopf das Anfrageprädikat unifizierbar ist, ist ' luke ' jacen. Es bleibt also elternt(X,Y) :- vater(X,Y) mit der Bindung vater(luce, jacen) zu beweisen. Prolog sucht also wieder eine Formel, mit deren Kopf dieses intern generierte Anfrageprädikat unifizierbar ist. Eine solche Formel existiert aber nicht in der Datenbasis. Das intern generierte Anfrageprädikat lässt sich endgültig nicht beweisen, weshalb Prolog nun einen Backtrack durchführt und eine andere Regel zur Unifikation mit der ursprünglichen Anfrage sucht. Es findet elternt(X,Y) :- mutter(X,Y) mit der Bin luke jacen, womit die intern generierte Anfrage mutter(luce, jacen) dung ' ' zu beweisen bleibt. Auch für diese Anfrage existiert keine unifizierbare Formel in der Datenbasis, weshalb Prolog wieder einen Backtrack zur ursprünglichen Anfrage durchführt. Eine weitere Formel, mit der die ursprüngliche Anfrage elternt(luke, jacen) zu unifizieren wäre, existiert allerdings nicht, womit diese Anfrage endgültig unbewiesen bleibt und Prolog No antwortet. 1. 2. 1.1. 1.2. 1.1’. elternt(luke, jacen) elternt(X,Y) :- vater(X,Y) X/luke, Y/jacen vater(luke, jacen) – Backtrack zu 1. elternt(luke, jacen) elternt(X,Y) :- mutter(X,Y) X/luke, Y/jacen mutter(luce, jacen) – Backtrack zu 1. elternt(luke, jacen) – kein Backtrack mehr möglich: No 10 Ein einfache Anfrage mit Variablen Ein interessanteres Ein-/Ausgabeverhalten erreicht man, wenn man in Anfragen Variable verwendet. Zum Beispiel: ?- elternt(darth_vader, Kind). Durch die Großschreibung von Kind erkennt Prolog, daß dies eine Variable ist und keine Konstante. Die Anfrage lautet sinngemäß also: “Existiert ein Kind, für dass gilt: Darth Vader ist ein Elternteil von Kind?” Als Ergebnis liefert Prolog nun: Kind = luke Mit dieser Antwort ist erstens die Anfrage bewiesen, zweitens aber auch zusätzlich eine mögliche Belegung der Variable angegeben, die die Anfrage beweisbar macht. Nun haben wir als Benutzer zwei Möglichkeiten: Entweder wir beenden die Anfragebearbeitung mittels RETURN. Oder wir veranlassen Prolog mittels Eingabe von ’;’, nach weiteren möglichen Belezu suchen. In diesem Fall führt Prolog ebenfalls ein Backtrack aus und macht gungen für die letzte Unifikation rückgängig: Kind = luke ; Kind = leia ; No ?- Als weitere Belegung liefert Prolog Kind = leia, woraufhin wir nochmals ; eingeben und nun als Antwort No und den Prompt bekommen, da es keine weitere Belegung von gibt, die die Anfrageformel beweisbar macht. Diese Funktionalität von Prolog legt eine alternative ) Lesart von Anfragen mit Variablen nahe: Nicht mehr “Existieren , so dass ...”, sondern “Liste mir (alle möglichen) Belegungen von auf, für die gilt, dass ...”. Die Abarbeitung von Prolog läuft in diesem Beispiel-Fall folgendermaßen ab: 11 1. 2. elternt(darth_vader, Kind) elternt(X,Y) :- vater(X,Y) X/darth_vader, Kind/Y vater(darth_vader, Y) vater(darth_vader, luke) Y/luke 3. 2.1. Kind = luke (durch ; Backtrack zu 2.) vater(darth_vader, Y) vater(darth_vader, leia) Y/leia 2.2. 2.1’. 1.1. 1.2. Kind = leia (durch ; Backtrack zu 2.) vater(darth_vader, Y) – Backtrack zu 1. elternt(darth_vader, Kind) elternt(X,Y) :- mutter(X,Y) X/darth_vader, Kind/Y mutter(darth_vader, Y) – kein Backtrack mehr möglich: No Wie kommt nun z.B. die Antwort Kind = luke zustande, wenn die letzte Unifikation, die zu dieser Antwort führt, Y/luke war (siehe Zeile 2.)? Was der Benutzer natürlich als Antwort erwartet, ist die Belegung der Variablen in seiner Anfrage. Diese werden auf dem Beweisweg alle gebunden (durch Unifikation). Falls die Anfrageliste zu einem Zeitpunkt leer ist, die Anfrage also bewiesen ist, sind alle Variable aller (auch intern) generierten Anfragen auf diesem Beweisweg gebunden. Anhand dieser Bindungen berechnet Prolog dann die Belegung der Variablen in der Benutzeranfrage transitiv zurück. Im Beispiel gelten die Bindungen Y/luke (Zeile 2.) und Kind/Y (Zeile 1.) und damit Kind/luke. Prolog und Tiefensuche In der letzten Beispielbearbeitung einer Anfrage ist bereits deutlich geworden, auf welche Art und Weise Prolog nach Lösungen sucht, nämlich mittels Tiefensuche. Nähere Ausführung folgt... 2.1.5 Kalküle – Sinnfreies :-) Beweisen Warum kann Prolog Formeln beweisen, deren Bedeutung es gar nicht kennt? Kalküle, Korrektheit, Vollständigkeit kurz erläutern. 12 3 Weitergehende Konzepte Zwei wichtige Konzepte sind bisher unerwähnt geblieben: Rekursive Regeln, also solche, deren Körper Prädikate enthält, die mit demselben Relationssymbol gebildet sind, wie der Kopf der Regel. Und Listen als spezielle Terme und Datenstruktur und ihre Verarbeitung mittels Prolog. 3.1 Rekursive Regeln Erweitern wir unser Beispielprogramm um zwei Regeln für eine neue Relation. vorfahr(X,Y) :- elternt(X,Y). vorfahr(X,Z) :- elternt(X,Y), vorfahr(Y,Z). Die erste Regel ist klar: Jemand ist Vorfahr von jemand anderem, wenn er Elternteil von ihm ist. Die zweite Regel besagt, dass jemand ( ) Vorfahr von jemand anderem ( ) ist, wenn er ( ) Elternteil von jemand drittem ( ) ist, der Vorfahr von ist. Eine Beispielanfrage und Prologs Antwort: ?- vorfahr(shmi, jacen). Yes ?Warum ist Shmi Vorfahr von Jacen? Shmi ist Elternteil von Darth Vader, Darth Vader ist Elternteil von Leia und Leia ist Elternteil von Jacen. Da Leia Elternteil von Jacen ist, ist sie auch ein Vorfahr von ihm (das besagt die erste Regel). Da Darth Vader Elternteil von Leia ist und sie wiederum Vorfahr von Jacen, ist auch er Vorfahr von Jacen (laut zweiter Regel). Da Shmi Elternteil von Darth Vader ist und dieser Vorfahr von Jacen, ist auch Shmi Vorfahr von Jacen (wieder laut zweiter Regel). Fertig. Überlegt Euch selbst, wie Prolog diese Abfrage abarbeitet. Die Regel vorfahr(X,Z) :- elternt(X,Y), vorfahr(Y,Z) zeichnet sich nun dadurch von den bisherigen Regeln aus, dass in ihrem Körper ein Prädikat vorkommt, das mit demselben Relationssymbol gebildet ist wie der Kopf der Regel. Eine solche Regel heißt rekursiv. Rekursive Regeln sind sind essentiell in Prolog, da sie die einzige Möglichkeit darstellen, mit induktiv aufgebauten (also potentiell unendlichen) Datentypen wie Zahlen oder Listen effektiv umzugehen. Schleifen, wie man sie aus imperativen Programmiersprachen kennt, gibt es 13 in Prolog nicht. Aus der prädikatenlogischen Perspektive haben rekursive Regeln in Prolog allerdings einige “merkwürdige” Eigenarten, die durch Prologs Beweisverfahren, insbesondere die Tiefensuche, zustandekommen. Die Gefahr, die potentiell besteht, wenn man rekursive Regeln verwendet, ist die, dass die Bearbeitung einer Anfrage nicht terminiert. Dieses Problem ist ein Charakteristikum des Beweisverfahrens und nicht eines von “rekursiven Formeln” an sich. Aus prädikatenlogischer Sicht ist eine “rekursive Formel” eine Formel wie jede andere, zwischen “rekursiven” und “nicht rekursiven” Formeln wird überhaupt nicht differenziert, weshalb auch der Begriff “rekursiv” zur Einordnung von prädikatenlogischen Formeln gar nicht existiert. Ein Beispiel: + (3.1) # Diese Formel drückt die Transitivität von aus und ist eine ganz normale prädikatenlogische Formel, die keiner besonderen Behandlung bedarf. Aus Prolog-Perspektive jedoch ist diese Formel rekursiv und Bedarf deshalb einer besonderen Behandlung. In diesem Falle ist es sogar so, dass diese Formel zweckmäßigerweise als Regel überhaupt nicht notiert würde, da sie eine nicht-terminierende Anfragebearbeitung für bestimmte Fälle geradezu garantiert. Folgende Regeln sind zu beachten, wenn man von rekursiven Regeln Gebrauch macht: 1. Jede Relation, die mittels mindestens einer rekursiven Regel definiert ist, muss auch mittels mindestens einer nicht-rekursiven Regel bzw. eines Fakts definiert sein. 2. Jede rekursive Regel sollte mindestens ein Prädikat im Körper enthalten, das nicht mittels desselben Relationssymbols gebildet ist, wie der Kopf der Regel. 3. Nicht-rekursive Regeln einer Relation sind in der Datenbasis vor/über den rekursiven Regeln der Relation zu notieren. 4. Die Prädikate einer rekursiven Regel, die die Rekursivität dieser Regel nicht verursachen (nicht-rekursive Prädikate), sind im Körper der Regel vor denjenigen Prädikaten zu notieren, die die Rekursivität verursachen (rekursive Prädikate). Bevor wir diese Regeln begründen, schauen wir uns zwei Beispiele an: Die Vorfahr-Relation, die wir der Starwars-Datenbasis hinzugefügt haben, hält diese Regeln ein: Sie ist mittels einer rekursiven Regel und einer nicht-rekursiven Regeln, die über der rekursiven steht, definiert. Ferner enthält die rekursive Regel ein nicht-rekursives Prädikat, elternt(X,Y), das im Körper der Regel vor dem rekursiven Prädikat vorfahr(Y,Z) steht. Hingegen hält die Formel (3.1) diese Regeln nicht ein, da in ihrem Körper kein nicht-rekursives Prädikat vorkommt. Begründungen dieser Regeln folgen später. 3.2 Listen Die einzige Datenstruktur in Prolog sind Terme. Eine wichtige Art von Termen sind Listen. Was eine Liste ist, ist induktiv (also mittels Grundelementen und Konstruktoren) definiert: Die Konstante ist eine Liste (die leere Liste). 14 ist eine Liste, falls ein Term und eine Liste sind. Nichts sonst ist eine Liste. Offensichtlich dürfen nach dieser Definition die Elemente einer Liste wieder Listen sein, denn für die Elemente einer Liste ist ja gefordert, dass es Terme sein müssen (ohne weitere Einschränkung), dass es also insbesondere wieder Listen sein dürfen. Für die benutzerfreundlichere Verwendung von Listen gibt es in Prolog nun bestimmte Schreibweisen: Eine Liste * * lässt sich äquivalent schreiben als bzw. als * oder oder auch als . Allgemein gilt wird Kopf (Head) der Liste, wird Rest (Tail) der Liste genannt. Der Ope . rator wird also innerhalb von eckigen Klammern verwandt, um eine Liste mittels ihres Kopfes und ihrer Restliste zu bezeichnen. Das Komma hingegen, um eine Liste mittels einer Auflistung ihrer einzelnen Elemente zu bezeichnen. Dabei lassen sich diese Mittel beliebig kombinieren. 3.2.1 Wichtige Prädikate auf Listen Es gibt einige Standardfunktionen (bzw. Prädikate unter Prolog) auf Listen, zu denen gehören: last/2: Das letzte Element einer Liste berechnen. member/2: Vorkommen eines Elements in einer Liste prüfen. append/3: Zwei Listen konkatenieren. reverse/2: Eine Liste umdrehen. Die angegebenen Stelligkeiten bezeichnen die Stelligkeiten der jeweiligen Prolog-Prädikate. Falls man die entsprechende Funktionalität als Funktion realisiert (was in Prolog nicht geht, weil es in Prolog keine Funktionen gibt, was aber z.B. in funktionalen Programmiersprachen die Lösung wäre), wäre die Stelligkeit jeweils um eins reduziert. Die genannten Prädikate sind in Prolog bereits standardmäßig definiert und liefern in folgenden Fällen yes: last reverse member append , falls das letzte Element der Liste , falls , falls , falls in der Liste ist, vorkommt, die Konkatenation der Listen von hinten gelesen 15 ergibt. und ist, 3.2.2 Beispielanfragen Einige Beispiele für last: ?- last([a,b,c,d], Last). Last = d ; No ?- last([a,b,c,d], d). Yes ?- last([a,b,c,d], c). No Die erste auf, unter denen gilt, Anfrage ist zu lesen als:”Liste mir alle Belegungen für dass das letzte Element der Liste ist.” Prolog liefert die korrekte Belegung und antwortet No auf die Anforderung weiterer Belegungen. Die nächsten beiden Anfragen testen ein bestimmtes Element daraufhin, ob es das jeweils letzte einer Liste ist. member lässt sich analog verwenden. Einige Beispiele für append: ?- append([a,b], [c,d], Result). Result = [a, b, c, d] ; No ?- append(Init, [c,d], [a,b,c,d]). Init = [a, b] ; No ?- append(Init, Rest, [a,b,c,d]). Init = [] Rest = [a, b, c, d] ; Init = [a] Rest = [b, c, d] ; Init = [a, b] Rest = [c, d] ; 16 Init = [a, b, c] Rest = [d] ; Init = [a, b, c, d] Rest = [] ; No Mittels der ersten Anfrage wird die Konkatenation zweier Listen berechnet. An der zweiten Anfrage sieht man nun bereits, wie flexibel sich Prolog-Prädikate einsetzen lassen: Hier dient das append-Prädikat dazu, zu einer Liste und einer weiteren Liste, wobei letztere eine Konkaten ation einer unbekannten und der Liste sein soll, diese unbekannte Liste zu berechnen. Noch deutlicher wird das mit der dritten Anfrage, mit der alle Kombinationen zweier Listen berechnet werden, die konkateniert eine gegebene Liste als Ergebnis haben. Während Funktionen in funktionalen oder imperativen Programmiersprachen immer festgelegte Input-Parameter und einen festen Wertebereich haben, sind Prädikate unter Prolog “in beliebige Richtungen” einsetzbar. Jede Funktion (zum Beispiel append) lässt sich auch als Umkehrfunktion bzw. -relation einsetzen bzw. lässt sich – noch allgemeiner – beliebig instanziieren und berechnet dann passende Werte für die variabel gelassenen Parameter. 3.2.3 Implementationen für last und append Im folgenden Beispielimplementationen für last append: last([Last], Last). last([_ | R], Last) :- last(R, Last). append([], List, List). append([F | R], List, [F | Appended]) :- append(R, List, Appended). Beide Prädikate sind mittels einer rekursiven Regel und einemFakt für den Rekursionsabbruch das letzte Element der Liste implementiert. Der Fakt für last besagt, dass ein Element , also der Liste, die ausschließlich dieses Element enthält, ist. Die rekursive Regel behandelt nun den Fall, dass die Liste aus einem Kopf und einer Restliste (die als Sonderfall natürlich leer sein kann) besteht. Hier taucht das Symbol _ als ein spezielles Variablensymbol (für den Kopf der Liste) auf. _ steht für eine Variable, die nicht benannt zu sein braucht, da sie nur ein einziges Mal in der Regel vorkommt. In einem solchen Fall sollte man immer dieses spezielle Symbol verwenden. Die Regel besagt nun, dass dann das letzte Element dieser Liste _ ist, wenn das letzte Element der Restliste ist. Die Erläuterung für append sowie Prologs Abarbeitung einer Beispielanfrage folgen. 17 3.2.4 Listen sortieren Eine weitere Standardfunktionalität ist das Sortieren von Listen. In SWI-Prolog sind dafür einige Prädikate vordefiniert: sort/2, msort/2, keysort/2 und predsort/3. Jedes dieser Prädikate nimmt eine Liste als ersten Parameter und bindet an die Variable, die mit dem zweiten Parameter übergeben wird, die sortierte Liste. Bei sort werden Duplikate in der Liste entfernt, bei msort nicht. Bei keysort muss die zu sortierende Liste als Elemente Key-Value-Paare enthalten. Solche Key-Value-Paare werden mittels des Operators - gebildet und haben die Form key-value, z.B. 3-luke, wobei 3 dann der Key zum Value luke wäre. keysort sortiert die Liste dann an Hand der Keys. Die Ordnung, die zum Sortieren gebraucht wird, ist eine Ordnung über Termen. (Siehe SWI-Prolog-Manual, später hier mehr). Beispiele: ?- sort([leia, shmi, luke, darth_vader], Sorted). Sorted = [darth_vader, leia, luke, shmi] ; No ?- sort([leia, shmi, luke, darth_vader, luke], Sorted). Sorted = [darth_vader, leia, luke, shmi] ; No ?- msort([leia, shmi, luke, darth_vader, luke], Sorted). Sorted = [darth_vader, leia, luke, luke, shmi] ; No ?- keysort([3-leia, 1-shmi, 67-luke, 4-darth_vader], Sorted). Sorted = [1-shmi, 3-leia, 4-darth_vader, 67-luke] ; No ??- sort([[a,c,b], [a,c], [a,b], [a,b,c,d]], Sorted). Sorted = [[a, b], [a, b, c, d], [a, c], [a, c, b]] ; No ?- keysort([1-[a,c,b], 3-[a,c], 4-[a,b], 2-[a,b,c,d]], Sorted). Sorted = [1-[a, c, b], 2-[a, b, c, d], 3-[a, c], 4-[a, b]] ; 18 No ?- 3.3 Arithmetik Terme werden in Prolog in der Regel nicht ausgewertet. Wir verdeutlichen dies an Hand eines Beispiels. Gegeben sei folgende kleine Datenbasis, die die Relation einTerm/1 definiert: einTerm(3+6). einTerm(4+9). einTerm([a,b,c,d]). 3+6 und 4+9 sind offenbar Terme. Listen sind auch Terme. Die Relation klassifiziert also drei bestimmte Terme als Terme. Dass die Relation von daher nicht viel Sinn macht, weil sie alle anderen Terme nicht als Terme klassifiziert, obwohl der Name nahelegt, dass sie genau alle Terme als Terme klassifiziert, davon sehen wir ab. Nun stellen wir ein paar Anfragen: ?- einTerm(3+6). Yes ?- einTerm(3+7). No ?- einTerm(9). No ?- einTerm(X). X = 3+6 ; X = 4+9 ; X = [a, b, c, d] ; No ?In der ersten Anfrage wollen wir wissen, ob 3+6 ein Term ist und erhalten ein positive Antwort, was naheliegend ist, da es so in der Datenbasis steht. In der zweiten Anfrage wollen wir es für 3+7 wissen und erhalten eine negative Antwort, was ebenfalls naheliegend ist. In der dritten 19 Anfrage wollen wir wissen, ob 9 ein Term ist. Wenn es nun so wäre, dass Prolog arithmetische Terme auswerten würde, könnte man annehmen, dass Prolog positiv antworten sollte, da wir ja 3+7 als Term definiert hatten und 3+7 9 ergibt. Da Prolog Terme aber in der Regel nicht auswertet, sind 3+7 und 9 verschieden. In der letzten Anfrage lassen wir uns die Relation auflisten und sehen auch hier, dass Prolog die Terme nicht auswertet. Eine Auswertung von Termen erreicht man mit der vordefinierten Relation is/2, die in InfixSchreibweise benutzt wird: ?- X is 3+7. X = 10 ; No ?- 10 is X+Y. ERROR: Arguments are not sufficiently instantiated ?- 10 is X+6. ERROR: Arguments are not sufficiently instantiated ?Wie man sieht zeichnet sich diese Relation von üblichen Relationen dadurch aus, dass man nicht beliebige Parameter spezifizieren darf und beliebige unspezifiziert lassen kann und dann die unspezifizierten von Prolog belegt bekommt. Sondern die is-Relation verlangt, dass auf der rechten Seite ein Term ohne Variable steht. Dieser wird dan ausgewertet und das Ergebnis an die Variable auf der linken Seite gebunden. 3.4 Der Cut Der Cut ist eine Möglichkeit, die Abarbeitung von Anfragen an Prolog zu beeinflussen. Genauer gesagt ist es eine Möglichkeit, das Backtracking an bestimmten Punkten zu verhindern. 3.5 Negation folgt später. 3.6 bagof, setof, findall bagof/3, setof/3 und findall/3 sind vordefinierte Prädikate, die in verschiedenen Varianten die Funktionalität implementieren, zu einem Term eine Liste aller seiner Instanzen, die zu einer gegebenen Konjunktion “passen”, zu berechnen. Eine Beispielanfrage: 20 ?- bagof(LeiasKind, mutter(leia, LeiasKind), Kinder). LeiasKind = _G158 Kinder = [jacen, jaina] ; No ?Diese Anfrage hat folgende Bedeutung:”Binde an die Variable Kinder die Liste aller Belegungen der Variable LeiasKind, mit denen das Prädikat mutter(leia, LeiasKind) gilt. Prologs Antwort ist Kinder = [jacen, jaina]. Das war auch zu erwarten, denn Prolog würde auf die Anfrage mutter(leia, LeiasKind) ja zunächst LeiasKind = jacen und anschließend, falls wir ein ’;’ eintippen würden, LeiasKind = jaina antworten und wiederum nach einem ’;’ No, da es keine weiteren gültigen Belegungen gibt, antworten. jacen und jaina sind die beiden Belegungen für LeiasKind, mit denen mutter(leia, LeiasKind) bewiesen werden kann. Der erste Parameter für bagof ist also der Term, deren konkrete Instanzen in einer Liste, die durch den dritten Parameter benannt wird, “passend” zu dem Prädikat bzw. der Konjunktion von Prädikaten, das/die im zweiten Parameter benannt wird, aufgelistet werden sollen. Das ergibt natürlich nur Sinn, wenn sich der Term (in unserem Beispiel einfach die Variable LeiasKind) und die Konjunktion (in unserem Beispiel das einzelne Prädikat mutter(leia, LeiasKind)) Variable teilen (in unserem Beispiel die Variable LeiasKind). “Passend” meint dann, dass die Variable des Terms (im Beispiel LeiasKind ) mit Belegungen instanziiert werden, unter denen die Konjunktion gültig ist. Ein weiteres Beispiel: ?- bagof([DarthsKind, LeiasKind], | (vater(darth_vader, DarthsKind), mutter(leia, LeiasKind)), | KinderKombinationen). DarthsKind = _G157 LeiasKind = _G160 KinderKombinationen = [[luke, jacen], [luke, jaina], [leia, jacen], [leia, jaina]] ; No ?Die Anfrage bedeutet:”Binde an die Liste KinderKombinationen alle Instanzen des Terms/der Liste [DarthsKind, LeiasKind], so dass für die entsprechenden Belegungen der Variablen die Konjunktion (vater(darth_vader, DarthsKind), mutter(leia, LeiasKind)) gilt.” 21 In den bisherigen zwei Beispielen kamen alle Variable, die in der Konjunktion vorkamen, auch im Term, dessen Instanzen berechnet werden sollten, vor. Nun ein Beispiel für den Fall, dass diese Bedingung nicht gegeben ist: ?- bagof(Kind, elternt(Elternt, Kind), Kinder). Kind = _G158 Elternt = darth_vader Kinder = [luke, leia] ; Kind = _G158 Elternt = shmi Kinder = [darth_vader] ; Kind = _G158 Elternt = leia Kinder = [jacen, jaina] ; No ?Die Anfrage lautet sinngemäß:”Binde an die Liste Kinder die Liste aller Instanzen von Kind, für die elternt(Elternt, Kind) gilt.” Die Variable Elternt kommt nun in dem Term (hier die Variable Kind) dessen Instanzen berechnet werden sollen, nicht vor. In diesem Falle berechnet bagof nun eine Liste von Instanzen von Kind für die erste gefundene gültige Belegung von Elternt. Wenn wir dann unsererseits ein ’;’ eingeben, veranlassen wir Prolog nach weiteren gültigen Belegungen für Elternt zu suchen und uns wieder eine, zu dieser Belegung passende, Liste von Belegungen für Kind zu berechnen usw.. setof unterscheidet sich von bagof dadurch, dass Duplikate, sofern sie auftreten würden, aus der berechneten Liste entfernt werden und dass die Liste sortiert ist (zu einer bestimmten Ordnung über Termen). Falls keine passende Instanz des Terms gefunden wird, antwortet Prolog bei bagof und setof mit No. findall unterscheidet sich von bagof dadurch, dass, falls in der Konjunktionen Variable vorkommen, die nicht in dem Term, dessen Instanzen berechnet werden sollen, vorkommen, nicht jeweils eine Liste für jede Belegung der nicht im Term vorkommenden Variablen berechnet wird. Es wird in diesem Fall eine einzige Liste berechnet, in der alle passenden Instanzen des Terms aufgelistet sind, für die es (irgend)eine gültige Belegung von der nicht im Term vorkommenden Variablen gibt. Beispiel: 22 ?- findall(Kind, elternt(Elternt, Kind), Kinder). Kind = _G158 Elternt = _G157 Kinder = [luke, leia, darth_vader, jacen, jaina] ; No ?Die Anfrage ist dieselbe wie die vorige, nur mit findall anstelle von bagof. Die Kinder werden nun nicht nach Elternteil separiert aufgelistet, sondern alle gemeinsam in einer einzigen Liste. Falls man diesen Effekt auch mit bagof und setof erreichen will, muss man die Variable, die nicht im Term vorkommt und nach der dennoch nicht separiert werden sol (hier elternt) extra auszeichnen. Das geschieht mittels des Operators ^. Beispiel: ?- bagof(Kind, Elternt^elternt(Elternt, Kind), Kinder). Kind = _G158 Elternt = _G157 Kinder = [luke, leia, darth_vader, jacen, jaina] ; No ?Nochmal dieselbe Anfrage wie die vorletzte, diesmal in der Variation, dass Elternt derart ausgezeichnet ist, dass nicht nach dieser Variablen separiert werden soll. Ein weiterer Unterschied von findall zu bagof und setof ist der, dass Prolog in dem Fall, dass keine Instanz gefunden werden kann, bei findall die leere Liste berechnet und nicht No antwortet. 23