11/12: weitere Prädikate
Manipulation der Wissensbasis
Prolog stellt Prädikate zur Verfügung, die eine Manipulation der Wissensbasis während der Laufzeit eines Programms ermöglichen: assert/1, (asserta/1, assertz/1), retract/1, retractall/1
% assert(a/z)/1:
% assert(Clause) fügt die Klausel Clause zur Wissensbasis dynamisch hinzu.
% asserta(Clause) fügt die Klausel als neue erste Klausel hinzu.
% assertz(Clause) fügt die Klausel als neue letzte Klausel hinzu.
% retract/1:
% retract(Clause) löscht eine Klausel, die mit Clause matcht, aus der Wissensbasis.
% retractall/1:
% retractall(Clause) löscht alle Klauseln, die mit Clause matchen.
Beispiel:
Im Prinzip ist es mit den Manipulationsprädikaten möglich, direkt von der Konsole zu programmieren:
?- assert(sterblich(X):- mensch(X)). true.
?- assert(mensch(mia)).
true.
?- assert(sterblich(garfield)). true.
?- sterblich(X). X = mia;
X = garfield.
Dies ist jedoch nur selten eine gute Idee, da sie genau aufpassen müssen, in welcher Reihenfolge sie die Klauseln einfügen.
Hinzufügen & Löschen von Klauseln
?- assert(mensch(anna)).
true.
?- asserta(mensch(tom)).
true.
?- assert(mensch(mia)).
true.
?- assertz(mensch(otto)).
true.
?- listing(mensch/1).
:- dynamic mensch/1. mensch(tom).
mensch(anna). mensch(mia).
mensch(otto). true.
?- retract(mensch(mia)).
true.
?- retract(mensch(X)).
X = tom. true.
?- listing(mensch/1). :- dynamic mensch/1.
mensch(anna). mensch(otto).
true.
?- retractall(mensch(X)).
true.
?- listing(mensch/1).
:- dynamic mensch/1. true.
copy_term/2: Anwendung von assert/1 und retract/
copy_term(Term1,Term2) erstellt in Term2 eine Kopie von Term1 mit neuen Variablen.
% Praedikate, die waehrend der Laufzeit veraendert werden sollen,
% muessen als dynamisch deklariert werden.
:- dynamic temporaer/1.
copy_term(T1,T2):-
assert( temporaer(T1)),
retract(temporaer(T2)).
?- copy_term(vorfahr(A,B),Z).
Z = vorfahr(_G1, _G2).
?- copy_term(vorfahr(A,B),Z1),copy_term(vorfahr(A,B),Z2).
Z1 = vorfahr(_G1, _G2),
Z2 = vorfahr(_G3, _G4).
Programmierung durch Memoisierung
Die Programmiertechnik Ergebnisse in der Wissensbasis zu speichern, um bei späteren Berechnungen darauf zurück greifen zu können, heißt Memoisierung.
Beispiel: Fibonacci-Zahlen ohne Memoisierung
fib(0) = 0, fib(1) = 1, fib(n) = fib(n − 2) + fin(n − 1)
fib(0,0). % fib(0)=0
fib(1,1). % fib(1)=1
% fib(n)=fib(n-1)+fib(n-2)
fib(N,Res) :-
N>1,
N1 is N-1,
N2 is N-2,
fib(N1,R1),
fib(N2,R2),
Res is R1+R2.
% Testen sie die folgenden Anfragen:
?- fib(4,R).
?- fib(25,R).
?- fib(40,R).
% Warum ist das Programm so langsam?
Vorsicht
Die Manipulation der Wissensbasis ist eine sehr mächtige Programmiertechnik.
Aber, sie
- kann die Deklarativität eines Programms zerstören,
- kann zu schwer lesbarem Code führen,
- sollte mit Bedacht eingesetzt werden.
Insbesondere sollte immer überprüft werden, ob zu einem dynamischen Prädikat bereits Klauseln in der Wissensbasis (durch zum Beispiel abgebrochene vorangegangene Aufrufe) stehen.
Fibonacci-Zahlen mit Memoisierung
fib(0) = 0, fib(1) = 1, fib(n) = fib(n − 2) + fin(n − 1)
% fib/2 muss als dynamisches
% Praedikat deklariert werden:
:- dynamic(fib/2).
fib(0,0):-!. % fib(0)=0
fib(1,1):-!. % fib(1)=1
fib(N,Res) :-
N>1,
N1 is N-1,
N2 is N-2,
fib(N1,R1),
fib(N2,R2),
Res is R1+R2,
asserta(fib(N,Res):-!).
% Teste die folgenden Anfragen:
?- fib(40,R).
?- fib(100,R).
?- fib(1000,R).
% Durch Memoisierung können rekursive Programme effektiver werden.
Alle Lösungen einer Prologanfrage
- Auf Anfragen mit nicht instantiierten Variablen antwortet Prolog mit einer Variablenbelegung, die die Aussage wahr macht.
- Durch die Eingabe des Semikolons wird Prolog aufgefordert alternative Belegungen zu erzeugen.
- So können alle möglichen Lösungen nacheinander generiert werden.
food(sushi).
food(apple).
food(pudding).
mag(popeye,X):- food(X).
?- mag(popeye,X). X=sushi; X=apple; X=pudding;
false.
Mithilfe der eingebauten Prädikate findall/3, bagof/3 und setof/3 können alle Lösungen auf einmal generiert und in einer Liste gesammelt werden.
Sammlung aller Lösungen: bagof/3
Das Prädikat bagof(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.
- Gibt es kein solches Objekt so scheitert das Prädikat.
- Zusätzlich werden freie Variablen gebunden.
?- bagof(Y,figur(X,Y),L).
X = garfield,
L = [katze, 7];
X = popeye,
L = [mensch, 30];
X = snoopy,
L = [hund, 7];
false.
?- bagof(Y,figur(snoopy,Y),L).
L = [hund, 7].
?- bagof(Y,figur(hans,Y),L).
false.
?- bagof((X,Y),figur(X,Y),L).
L = [(garfield, katze), (snoopy, hund), (popeye, mensch),
(garfield, 7), (snoopy, 7), (popeye, 30)].
?- bagof(Y,X^figur(X,Y),L).
L = [katze, hund, mensch, 7, 7, 30].
?- findall(L, bagof(Y,figur(X,Y),L),List).
List = [[katze, 7], [mensch, 30], [hund, 7]].
?- findall(f(X,L), bagof(Y,figur(X,Y),L),List).
List = [f(garfield, [katze, 7]), f(popeye, [mensch, 30]),
f(snoopy, [hund, 7])].
Sammlung aller Lösungen: findall/3
Das Prädikat findall(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.
- Gibt es kein solches Objekt so wird L mit der leeren Liste unifiziert.
figur(garfield, katze).
figur(snoopy, hund).
figur(popeye, mensch).
figur(garfield,7).
figur(snoopy, 7).
figur(popeye,30).
?- findall(Y,figur(X,Y),L).
L = [katze,hund,mensch,7,7,30].
?- findall(name(X),figur(X,Y),L).
L = [name(garfield), name(snoopy), name(popeye),
name(garfield), name(snoopy), name(popeye)].
?- findall(Y,(figur(X,Y),number(Y)),L).
L = [7,7,30].
?- findall((X,Y),figur(X,Y),L).
L = [(garfield, katze),(snoopy, hund),(popeye, mensch),
(garfield, 7), (snoopy, 7), (popeye, 30)].
?- findall(Y,figur(snoopy,Y),L).
L = [hund, 7].
?- findall(Y,figur(hans,Y),L).
L = [].
Sammlung aller Lösungen: setof/3
Das Prädikat setof(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.
- Gibt es kein solches Objekt so scheitert das Prädikat.
- Zusätzlich werden freie Variablen gebunden.
- Doppelte Elemente in L werden entfernt.
- Die Elemente in L werden sortiert.
?- setof(X, Y^figur(X,Y),L).
[garfield,snoopy,popeye].
?- setof(Y,figur(X,Y),L).
X = garfield,
L = [katze, 7] ;
X = popeye,
L = [mensch, 30] ;
X = snoopy,
L = [hund, 7]; false.
?- setof(alter(Y), (figur(X,Y),number(Y)),L).
[alter(7),alter(30)].
?- setof(Y, figure(hans,Y), L).
false.
Dateien
Dateiausgabe mit open/3
- In Prolog können Ausgaben (Streams) mit dem eingebauten Prädikat open/3 in Dateien umgeleitet werden.
- Das Prädikat nimmt als erstes Argument einen Dateinamen. Das zweite Argument bestimmt den Modus (read, write, execute, default, all). Zuletzt wird eine Variable als Stream instanziiert.
write_file :-
open(’test.txt’,write,FStream),
write(FStream,’hello’),
nl(FStream),
close(FStream).
?- write_file.
Prolog-Programme ausführen (aus Python)
- Prolog-Programme können aus anderen Programmiersprachen aufgerufen werden.
- Das nachfolgende Beispiel beschreibt einen möglichen Prolog-Aufruf aus Python heraus.
import subprocess
pl_file = "path/prolog_file.pl"
p = subprocess.Popen(["swipl","-q","-s",pl_file,"-g","main,halt", \ "-t","nl,halt"],stdout=subprocess.PIPE)
p.wait()
stdout,stderr = p.communicate()
Dateien einlesen mit consult/1
- Wichtig: Die einzulesende Datei muss sich in Prolog-Notation befinden!
- Daten können bspw. in Form von Prädikaten eingelesen werden.
/* file.pl */
data(1,popeye).
data(1,pluto).
data(2,goofy).
data(2,mia).
data(1,anna).
load_file :- consult(’file.pl’).
?- load_file, findall(T,data(1,T),L).
L = [popeye, pluto, anna].
Module
- ermöglichen eine strukturierte Programmierung,
- verstecken Hilfsprädikate,
- erleichtern das Programmieren in Teams.
Libraries
- Libraries sind Module, die wichtige Prädikate zusammenfassen.
- Im SWI-Manual finden sie eine Liste der wichtigsten Libraries.
:- use_module(library(lists)).
Arbeit mit Modulen
Deklarieren von Modulen:
% Datei tools.pl
% Modulname: tools
% oeffentliche Praedikate: my_reverse/2, my_member/2
:- module(tools, [my_reverse/2, my_member/2]).
my_reverse(Liste,R) :-
reverse_acc(Liste,[],R).
reverse_acc([],Acc,Acc).
reverse_acc([H|T],Acc,R) :-
reverse_acc(T,[H|Acc],R).
my_member(X,[X|T]).
my_member(X,[_|T]):- my_member(X,T).
Aufruf von Modulen :
% Aus dem Modul reverse werden alle oeffentlichen Praedikate importiert
:- use_module(tools).
% Aus dem Modul reverse wird das Praedikat reverse/2 importiert
:- use_module(tools, [my_reverse/2]).
Graphviz: dot-Format
Graphviz ist eine Software zur Visualisierung von Graphen (http://www.graphviz.org/).
Zur Beschreibung von gerichteten Graphen wird das dot-Format verwendet:
digraph G {
main -> parse;
main -> init;
main -> cleanup;
parse -> execute; execute -> make_string;
execute -> printf
init -> make_string;
main -> printf;
execute -> compare;
}
Grafische Ausgabe eines Ableitungsbaums
Ziel: Erzeuge zu einem Baum in der Prolog-Term-Notation eine Beschreibung des Baums im dot-Format und generiere daraus ein Bild des Baums.
tree(1,s(np(det(die), n(katze)), vp(v(klaut), np(det(die), n(maus))))).
Generiere für jeden Knoten eine dot-Knotendefinition:
q1[label=np];
Generiere für jede Mutter-Tochter-Beziehung eine dot-Kante:
q2->q3;
So generiert mgraph(1) die dot-Datei graph.dot und die jpg-Datei graph.jpg
digraph G { q0[label=s]; q1[label=np]; q2[label=det]; q3[label=die];
q2->q3;
q1->q2; q4[label=n]; q5[label=katze];
q4->q5;
q1->q4;
q0->q1; q6[label=vp]; q7[label=v]; [...]
}

Aufruf von der Shell:
$ dot graph.dot -Tjpg -o graph.jpg

Graphviz/DOT-Darstellung eines Ableitungsbaums
:- dynamic(node/1).
% Graphviz/DOT (graphviz liegt im Unterordner gvdot)
dotjpeg :- shell(’gvdot/dot.exe output.dot -Tjpg -o graph.jpg’).
% Hauptaufruf
mgraph(N) :-
open(’output.dot’,write,FS),
tree(N,T),
pdot(T,FS),
close(FS),
dotjpeg.
% Initialisierung des Tab - Zaehlers.
pdot(Term,FS):-
retractall(node(_)),
write(FS,’digraph G {’),
nl(FS),
assert(node(0)),
pdot(0,Term,FS),
write(FS,’}’).
% Baum drucken.
pdot(N,Term,FS):-
Term =.. [F| Args ], % Struktur zu Liste.
tab(FS,4),
write(FS,q),
write(FS,N), write(FS,’[label=’),
write(FS,F),write(FS,’];’),
nl(FS), % Ausgabe des Mutterknotens N1 is N+1,
retract(node(_)),
assert(node(N1)),
pdot1(N,Args,FS). % Unterbaeume drucken.
% Unterbaeume drucken .
pdot1(Nmother,[H|T],FS):-
node(Nchild),
pdot(Nchild,H,FS), % Drucke eine Schwester.
tab(FS,4),
write(FS,q),
write(FS,Nmother),
write(FS,’->’),
write(FS,q),
write(FS,Nchild),
write(FS,’;’),
nl(FS),
pdot1(Nmother,T,FS). % Drucke die anderen Schwestern .
pdot1(_,[],_FS). % Termination .
% Beispielbaeume
tree(1,s(np(det(die), n(katze)), vp(v(klaut), np(det(die),
n(maus))) tree(2,np(det(die), n(katze))).
Zusammenfassung
- Wir haben gelernt, wie wir dynamisch die Wissensbasis verändern können.
- Wir haben wichtige Prädikate zur Aufsammlung aller Lösungen einer Anfrage kennengelernt.
- Wir können Ergebnisse in Dateien schreiben und Dateien mit Prologklauseln einlesen.
- Wir haben gesehen, wie wir ein Prologprogramm in Module zerlegen können.