II. Prinzipien der Auswertung von PROLOG-Programmen
- Lineare Resolutionsbeweise
- Beweisbäume
- Auswahl einer Klausel, Auswahl eines Literals
- Das "Cut" zur Steuerung der Suche
- Probleme der Negation
- PROLOG und die Logikprogrammierung
© 2001
François Bry
Dieses Lehrmaterial wird ausschließlich zur privaten Verwendung
angeboten. Eine nichtprivate Nutzung (z.B. im Unterricht oder eine
Veröffentlichung von Kopien oder Übersetzungen) dieses Lehrmaterials
bedarf der Erlaubnis des Autors.
gehe_schwimmen :- es_regnet, arbeite_nicht.
gehe_schwimmen :- sonne_scheint, habe_zeit.
habe_zeit :- arbeite_nicht.
arbeite_nicht :- wochenende.
arbeite_nicht :- feiertag.
wochenende :- samstag.
wochenende :- sonntag.
feiertag :- weihnachten.
feiertag :- ostern.
sonne_scheint.
samstag.
gehe_schwimmen
kann unter Anwendung des Programms
P9 wie folgt bewiesen werden,
wobei jede Liste einem Zustand im Beweis entspricht:
< gehe_schwimmen >
< sonne_scheint, habe_zeit >
< habe_zeit >
< arbeite_nicht >
< wochenende >
< samstag >
< >
gehe_schwimmen
, bewiesen.
< gehe_schwimmen >
< es_regnet, arbeite_nicht >
es_regnet
weder im Kopf einer Regel noch als Faktum
in P9 vorkommt.
Sei A = A1, ..., An
eine
negationsfreie variablenfreie Anfrage.
A1 :- L1, ..., Lm
eine Regel in P, so ist
A1, A2, ..., An --->
L1, ..., Lm, A2, ..., An
ein Resolutionsschritt aus A =
A1, ..., An
bezüglich
P.
A1
ein Faktum in P, so ist
A1, A2, ..., An ---> A2, ..., An
ein Resolutionsschritt aus A =
A1, ..., An
bezüglich P.
A0 ---> A1 ---> . . . ---> Ak
wobei A0 = A ist, Ak ist die
leere Anfrage, und für alle i von 1 bis k ist
Ai-1 ---> Ai
ein Resolutionsschritt.
Unifikationsalgorithmus
p(t1, ..., tn)
und
p(s1, ..., sn)
zwei Atome.
Algorithmus zur Unifikation von
p(t1, ..., tn)
und
p(s1, ...,
sn)
:
GS := {t1 = s1, ..., tn = sn}
GS
den Wert "scheitert"
hat,
sind
p(t1, ..., tn)
und
p(s1, ..., sn)
nicht unifizierbar.
Andernfalls ist GS
ein allgemeinster Unifikator der
beiden Atome.
{t = X} U G ---> {X = t} U G
t
keine Variable ist.
{X = X} U G ---> G
{f(u1,...,uk) =
g(v1,...,vl)} U G
---> "scheitert"
f
verschieden von g
ist
oder k
verschieden von l
(klein L
) ist.
{f(u1,...,uk) =
f(v1,...,vk)} U G --->
{u1 = v1, ...,
uk = vk)} U G
{X = t} U G ---> {X = t} U G'
X
in G
vorkommt.
Dabei entsteht
G'
aus
G
durch Ersetzen
von jedem Vorkommen der Variablen X
durch den Term
t
.
sigma = {X1 = t1, ...,
Xn = tn}
definiert eine Funktion über
Terme, Atome und im Allgemeinen Ausdrücke,
die jeden Ausdruck A auf den
Ausdruck abbildet, der sich aus A ergibt, wenn alle
Vorkommen einer
Variablen Xi
durch ti
ersetzt werden.
R
eine Regel oder ein Faktum in einem Programm P,
so nennt man Variante von R
die Regel oder das Faktum, das
sich aus R
ergibt, wenn die in R vorkommenden Variablen
konsistent durch andere ersetzt werden, so daß keine zwei Variablen von
R
durch die gleiche Variable ersetzt werden.
A1, ... , An
eine negationsfreie
Anfrage.
R = B :- L1, ..., Lm
eine Variante einer Regel in P,
die keine gemeinsame Variable mit A besitzt,
und ist sigma
ein allgemeinster Unifikator von
A1
und B
, so ist
A1, A2, ..., An --->R / sigma
sigma(L1), ..., sigma(Lm), sigma(A2), ..., sigma(An)
ein Resolutionsschritt aus A bezüglich P.
B
eine Variante eines Faktums in P,
die keine gemeinsame Variable mit A besitzt,
und ist sigma
ein allgemeinster Unifikator von
A1
und B
, so ist A1, A2, ..., An --->B / sigma
sigma(A2), ..., sigma(An)
ein Resolutionsschritt aus A bezüglich P.
Ein linearer Resolutionsbeweis von einer negationsfreien Anfrage A bezüglich eines negationsfreien Programms P ist eine Sequenz
p(X,Y) :- q(X), r(Y).
q(Y) :- s(Y).
s(a).
r(b).
p(a,b)
soll erfolgreich sein. Wären keine
Varianten der Regel oder Fakten in der Definition von linearen
Resolutionsbeweise berücksichtigt, dann gäbe es aber keinen linearen
Resolutionsbeweis von p(a,b)
bezüglich
P11
, weil die Variable Y
sowohl an b
als auch an a
gebunden werden müßte.
p :- q, r, s.
q :- s.
r :- t, u.
s.
t.
u.
p
|
+-----------+-----------+
| | |
q r s
| |
s +---+---+
| |
t u
Bei der Suche nach einem linearen Resolutionsbeweis werden die Fakten und Regeln des Programms in der Reihenfolge probiert, wie sie im Programm stehen.
Diese feste Reihenfolge gilt für alle PROLOG-Systeme.
Sowohl die Reihenfolge der Bearbeitung der Anfrageatome als auch der Programmregeln und -fakten kann durch Veränderung der Aufschreibreihenfolge beeinflußt werden, was in manchen Fällen vorteilhaft ist.
verbunden(X,Y) :- kante(X,Y).
verbunden(X,Y) :- kante(X,Z), verbunden(Z,Y).
kante(k1,k2).
kante(k2,k3).
kante(k3,k1).
verbunden
der binären Relation kante
.
Auf die Anfrage verbunden(U,V)
liefert es nur fünf der neun möglichen Antworten,
weil es in eine Endlosschleife gerät,
in der drei dieser Antworten ständig wiederholt werden.
verbunden(X,Y) :- kante(X,Y).
verbunden(X,Y) :- verbunden(Z,Y), kante(X,Z).
kante(k1,k2).
kante(k2,k3).
kante(k3,k1).
verbunden(U,V)
verhält es sich aber anders:
Es berechnet zuerst alle neun Antworten,
wiederholt dies dann aber unendlich oft.
verbunden(X,Y) :- verbunden(Z,Y), kante(X,Z).
verbunden(X,Y) :- kante(X,Y).
kante(k1,k2).
kante(k2,k3).
kante(k3,k1).
verbunden
der binären Relation kante
, die
einen anderen Ablauf bei der Auswertung der Anfrage
verbunden(U,V)
hervorbringt: keine Verbindung wird berechnet und die Berechnung
verfällt sofort in eine unendliche Schleife.
verbunden(X,Y) :- kante(X,Z), verbunden(Z,Y).
verbunden(X,Y) :- kante(X,Y).
kante(k1,k2).
kante(k2,k3).
kante(k3,k1).
!
),
zur Steuerung der Suche.
member(X, [X|_]).
member(X, [_|T]) :- member(X, T).
weiblich(anna).
weiblich(suse).
maennlich(hans).
maennlich(paul).
maennlich(peter).
paritaetisch(L) :-
member(W, L), weiblich(W),
member(M, L), maennlich(M).
:- paritaetisch([suse, paul, anna, hans, peter]), A = A.
A = A More? (;)
A = A More? (;)
A = A More? (;)
A = A More? (;)
A = A More? (;)
A = A More? (;)
no (more) solution.
:-
!
) nach dem zweiten Rumpfatom
der Regel für
paritaetisch
hat die folgende Wirkung:
member(X, [X|_]).
member(X, [_|T]) :- member(X, T).
weiblich(anna).
weiblich(suse).
maennlich(hans).
maennlich(paul).
maennlich(peter).
paritaetisch(L) :-
member(W, L), weiblich(W),
!, % Cut hier
member(M, L), maennlich(M).
:- paritaetisch([suse, paul, anna, hans, peter]), A = A.
A = A More? (;)
A = A More? (;)
A = A More? (;)
no (more) solution.
:-
member(X, [X|_]).
member(X, [_|T]) :- member(X, T).
weiblich(anna).
weiblich(suse).
maennlich(hans).
maennlich(paul).
maennlich(peter).
paritaetisch(L) :-
member(W, L), weiblich(W),
member(M, L), maennlich(M),
!. % Cut hier
:- paritaetisch([suse, paul, anna, hans, peter]), A = A.
A = A
yes
:-
% polynom(P, X) : P ist ein Polynom in X.
:- op(35, yfx, **).
polynom(X, X) :- var(X), !.
polynom(K, X) :- konstante(K).
polynom(P1+P2, X) :- polynom(P1, X), polynom(P2, X).
polynom(P1-P2, X) :- polynom(P1, X), polynom(P2, X).
polynom(P1*P2, X) :- polynom(P1, X), polynom(P2, X).
polynom(P/K, X) :- polynom(P, X), konstante(K), K \= 0.
polynom(P**N, X) :- natuerliche_zahl(N), polynom(P, X).
konstante(K) :- integer(K).
natuerliche_zahl(N) :- integer(N), N >= 0.
op
zu erläutern. Das Systemprädikat op
dient
der Deklaration von Operatoren, d.h. von Relationssymbolen, die mit
Sonderzeichen bezeichnet werden, oder Post- oder Infix geschrieben werden.
Es wird wie folgt verwendet:
:- op(Präzedenz, Klammerung, Name)
:- op(21, yfx, +).
:- op(31, yfx, *).
2 + 3 * 5
als (2 + 3) * 5
gelesen wird.
Bei Infix-Operatoren, die notwendigerweise binäre Relationssymbole sind,
ist die Klammerung eines der Kürzel
xfx
, xfy
, yfx
und yfy
.
Bei Präfix-Operatoren, die notwendigerweise unäre Relationssymbole sind,
ist die Klammerung eines der Kürzel
fx
und fy
.
Bei Postfix-Operatoren, die notwendigerweise unäre Relationssymbole sind,
ist die Klammerung eines der Kürzel
xf
und yf
.
Ein x
bedeutet, daß der Operator an dieser Stelle
nicht ungeklammert vorkommen darf.
Ein y
bedeutet dagegen, daß der Operator an dieser Stelle
auch ohne Klammern wieder vorkommen darf,
und dann so behandelt wird als sei er geklammert.
:- op(35, xfx, **).
yes.
:- X = 2 ** 3 ** 5.
syntax error: bracket necessary
| X = 2 ** 3 ** 5.
| ^ here
:- op(35, xfy, **).
yes.
:- X = 2 ** 3 ** 5.
X = 2 ** 3 ** 5
yes.
:- A ** B = 2 ** 3 ** 5.
A = 2
B = 3 ** 5
yes.
:- op(35, yfx, **).
yes.
:- A ** B = 2 ** 3 ** 5.
A = 2 ** 3
B = 5
yes.
:- op(60, fx, nicht).
yes.
:- A = nicht nicht p.
syntax error: bracket necessary
| A = nicht nicht p.
| ^ here
:- op(60, fy, nicht).
yes.
:- A = nicht nicht p.
A = nicht nicht p
yes.
:-
fail
ist in PROLOG ein 0-stelliges Relationssymbol, das nicht
bewiesen werden kann, also immer scheitert. Die Kombination "Cut-Fail" ist
besonders interessant.
variablenfrei(Term) :- var(Term), !, fail.
variablenfrei(Term) :- atomic(Term), !.
variablenfrei(Term) :- Term =.. [F|Argumente],
variablenfrei_liste(Argumente).
variablenfrei_liste([]).
variablenfrei_liste([X|L]) :- variablenfrei(X), variablenfrei_liste(L).
:- p(a, b, c) =.. [X | Y].
X = p
Y = [a, b, c]
yes.
:- a =.. [X | Y].
X = a
Y = []
yes.
:- [] =.. [X | Y].
X = []
Y = []
yes.
:- X =.. [A | B].
instantiation fault in X =.. [A|B]
:-
variablenfrei(f(X))
scheitert mit P20,
ist aber mit P21 erfolgreich,
weil die erste Klausel von P21
in dem Fall erfolgreich ist.
variablenfrei(Term) :- not var(Term).
variablenfrei(Term) :- atomic(Term), !.
variablenfrei(Term) :- Term =.. [F|Argumente],
variablenfrei_liste(Argumente).
variablenfrei_liste([]).
variablenfrei_liste([X|L]) :- variablenfrei(X), variablenfrei_liste(L).
member(M, [M|_]).
member(M, [_|T]) :-
member(M, T).
is_member(M, [M|_]):- !.
is_member(M, [_|T]) :-
is_member(M, T).
:- member(X, [1, 2, 3]).
X = 1 More? (;)
X = 2 More? (;)
X = 3 More? (;)
no (more) solution.
:- is_member(2, [1, 2, 3]).
yes.
:- is_member(X, [1, 2, 3]).
X = 1
yes.
:-
member
, also ohne Cut,
anzuwenden. Soll aber nur getestet werden, ob ein bereits bekanntes Objekt in
einer (bereits bekannten) Liste vorkommt, dann kann das effizientere
is_member
verwendet werden.
\+
oder not
geschrieben,
wird folgendermaßen ausgewertet:
Um eine Anfrage der Form \+ A
auszuwerten,
wird zunächst in einer "Nebenrechnung"
die Anfrage A
ausgewertet.
Scheitert diese Auswertung in der "Nebenrechnung",
so gilt \+ A
als bewiesen.
Wird dagegen in der Nebenrechnung ein Beweis für A
gefunden,
dann scheitert die Auswertung von \+ A
.
angestellter(hans).
angestellter(maria).
angestellter(ingrid).
angestellter(peter).
spricht(hans, deutsch).
spricht(maria, deutsch).
spricht(ingrid, deutsch).
spricht(peter, deutsch).
spricht(maria, franzoesisch).
spricht(ingrid, franzoesisch).
spricht_nur_deutsch_1(X) :-
spricht(X, deutsch),
not spricht_eine_fremdsprache(X).
spricht_nur_deutsch_2(X) :-
not spricht_eine_fremdsprache(X),
spricht(X, deutsch).
spricht_eine_fremdsprache(X) :-
spricht(X, S), S \= deutsch.
:- spricht_nur_deutsch_1(X).
X = hans More? (;)
X = peter
yes.
:- spricht_nur_deutsch_2(X).
no (more) solution.
:-
spricht_nur_deutsch_1
funktioniert wie erwartet.
Zunächst wird X
durch die Auswertung von spricht(X,deutsch)
gebunden. Die so ermittelten Werte von X
werden dann mit dem negativen Literal gefiltert.
Dagegen ist
spricht_nur_deutsch_2
unkorrekt,
weil das negative Literal
not spricht_eine_fremdsprache(X)
in jedem Fall scheitert,
wenn X
keinen Wert hat.
Die prozedurale Definition der Negation in PROLOG
ermöglicht nicht,
daß ein negatives Literal Bindungen für Variablen berechnet.
Besonderheiten von PROLOG sind u. a. die Auswahlfunktionen zur Selektion der Regeln und Fakten aus dem Programm und des Literals aus der Anfrage, sowie die Tiefensuche.
PROLOG hat sogenannte nichtlogische Konstrukte,
die nicht unumstriten sind, darunter
!
, assert
, retract
.
Obwohl kritisiert, ist PROLOG sehr beliebt und wird häufig, auch in der Industrie, für das sogenannte "rapid prototyping" verwendet. Es gibt zahlreiche Erfolgsgeschichten über den industriellen Einsatz von PROLOG.
Letzte Änderung: 18. August 1998