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
emanuel.kitzelmann@wiai.uni-bamberg.de, 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