4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Beispiel 4.8. Reihenfolge von Klauseln • Bei der Programmierung in Prolog steht grundsätzlich die Repräsentation logischer Zusammenhänge im Vordergrund. • Nichtsdestotrotz ist es unvermeidbar, die Mechanismen der Abarbeitung in Prolog zu kennen und gegebenenfalls steuernd einzugreifen. Beispiel 4.7. Man betrachte die folgende Regel: listsum( [], 0 ). listsum( [H|T], Summe ) :- listsum( T, Sum1 ), Summe is Sum1 + H. fak(N, Fak ) :- N1 is N - 1, fak(N1, Fak1), Fak is Fak1 * N. fak(0,1). • Eine kürzere Schreibweise ist nicht möglich, da in Verbindung mit der Unifikation keine Auswertung eines arithmetischen Ausdrucks stattfinden kann. • Die Anfrage fak(7,X) führt zu einem Fehler. • Prolog läuft in einen unendlichen Rekursionszyklus, weil die erste Regel immer wieder anwendbar ist. • Grund: unvollständige Spezifikation 222 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Aus logischer Sicht ist die Reihenfolge der Klauseln listsum( T, Sum1 ) und Summe is Sum1 + H unerheblich. Für die Abarbeitung spielt die Reihenfolge dagegen eine Rolle. Betrachtet man listsum( [H|T], Summe ) :- Summe is Sum1 + H, listsum( T, Sum1 ). 224 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog • Abhilfe: Erweiterung der Regel fak(N, Fak ) :- integer(N), N > 0, N1 is N - 1, fak(N1, Fak1), Fak is Fak1 * N. fak(0,1). so würde dies zu einem Fehler führen. • Mit dem Prädikat integer/1 wird geprüft, ob das Argument eine Integerzahl ist. • So spielt auch die Reihenfolge der Regeln keine Rolle mehr. ?- listsum( [2,3,4], X ). ERROR: Arguments are not sufficiently instantiated Grund: Sum1 ist zum Zeitpunkt der Auswertung nicht mit einem Wert unifiziert (belegt). 223 225 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Beispiel 4.9. Man betrachte die folgenden Varianten des Pr ädikats vorfahr/2. 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Diese Dinge machen deutlich, daß auch in Prolog prozedurale Aspekte mit berücksichtigt werden müssen. Sonst führt dies zu Prolog-Programmen, die: 1. vorfahr( X, Y ) :- elternteil( X, Y ). vorfahr( X, Y ) :- elternteil( X, Z ), vorfahr( Z, Y ). • logisch (deklarativ) korrekt aber • prozedural inkorrekt sind. 2. vorfahr( X, Y ) :- elternteil( X, Y ). vorfahr( X, Y ) :- vorfahr( X, Z ), elternteil( Z, Y ). Stellt man eine Anfrage mit der zweiten Variante, so erhält man eine Fehlermeldung, falls die Vorfahrbeziehung nicht erfüllt ist: ?- vorfahr2( lars, lars ). ERROR: Out of local stack Ursache: In der zweiten Variante wird permanent vorfahr( lars, Z ) aufgerufen. Hierdurch kommt es zu einem Überlauf des Stacks. 226 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Probleme bei der Verwendung von Rekursion: 228 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Cut zur Kontrolle des Backtracking • Nicht-Termination Durch eine ungünstige Reihenfolge der Klauseln ensteht eine unendlicher Zyklus. • Speicherprobleme Zur Vermeidung dieser Probleme sollte man nach Möglichkeit die folgenden Grundprinzipien beachten: Prolog sucht mittels Backtracking bei der Abarbeitung nach Alternativen. Beispiel 4.10. Gegeben seien die folgenden Prolog-Regeln zur Klassifikation von Einkommen: einkommen( X, gering ) :- X < 1000. einkommen( X, mittel ) :- X >= 1000, X =< 2000. einkommen( X, hoch ) :- X > 2000. • Einfache Dinge zuerst! Hierdurch findet eine Filterung für die aufwendigen Berechnungen statt. • Einsatz von Endrekursion Die rekursiven Aufrufe sollte als letzte Klausel des Regelrumpfs erfolgen. Es wird die Anfrage gestellt: ?- einkommen( 950, B ), B = hoch. 227 229 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Es passiert folgendes: Sei eine Klausel der folgenden Struktur gegeben: • X wird mit 950 unifiziert, B mit gering. Der Regelrumpf X < 1000 ist erfüllt, das Anfrageliteral B = hoch nicht. • Mittels Backtracking wird die zweite und dritte Regel ausprobiert. • Es werden jeweils die Unifikationen durchgeführt und der Regelrumpf getestet. Der Regelrumpf ist nicht erfüllt. • Ausgabe: No. H :- B1,..., Bn, !, A1,..., Am. Das Backtracking ist hier vollkommen unnötig, da für ein Einkommen nur genau eine Regel in Frage kommt. Sind B1,..., Bn erfüllt, so werden alle Alternativen, d.h. alle eventuell noch anwendbaren Regeln zum Beweis für B1,..., Bn und H abgeschnitten. Beispiel 4.12. Das Prolog-Prädikat für den Test, ob ein Element in einer Liste enthalten ist: memberchk( X, [X|_] ) :- !. memberchk( X, [_|Y] ) :- memberchk( X, Y ). 230 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 232 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Mit dem Cut (geschrieben !) wird Backtracking verhindert. Der Cut kann die Bedeutung eines logischen Programms verändern. Beispiel 4.11. p :- a,b. p :- c. Entspricht der Implikation einkommen( X, gering ) :- X < 1000, !. einkommen( X, mittel ) :- X >= 1000, X =< 2000, !. einkommen( X, hoch ) :- X > 2000. a∧b∨c→p Wird der Cut ! überschritten, findet kein Backtracking mehr statt. p :- a,!,b. p :- c. Entspricht der Implikation a ∧ b ∨ ¬a ∧ c → p • Ein Cut, der die Bedeutung eines Programms verändert, heißt roter Cut. • Ein Cut, der die Bedeutung eines Programms nicht verändert, heißt grüner Cut. Der Cut drückt hier folgendes aus: Diese Regel ist genau die richtige, es kommt keine andere in Frage. Dementsprechend würde die Anfrage des letzen Beispiels effizienter abgearbeitet. 231 233 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog • fail/0 ist ein vordefiniertes Prädikat, das nie erfüllt ist. Hierdurch wird Backtracking erzwungen. • Ist elternteil( X, _ ) erfüllt, so unterdrückt der Cut aber das Backtracking und bewirkt das Abschneiden der zweiten Regel. Negation • Bisher haben wir mit Prädikaten immer “positive” Sachverhalte ausgedrückt: Ist Elternteil von, ist Vorfahr von, etc. • Es kann aber auch notwendig sein, einen “negativen” Sachverhalt auszudrücken, d.h. etwas gilt nicht: Hat keine Kinder, etc. Beispiel 4.13. Man möchte für das Verwandte-Beispiel ein Prädikat für die Kinderlosigkeit definieren: Dieses Prinzip wird in Prolog allgemein für die Negation benutzt. Die Negation eines Prädikats Ziel entpricht prinzipiell folgenden Regeln: not( Ziel ) :- Ziel , !, fail. not( Ziel ). • Solch ein Prädikat not/1 ist in Prolog enthalten. • Das Argument von not/1 ist eine Formel und kein Term! kinderlos( X ) :- elternteil( X, _ ), !, fail. kinderlos( X ). 234 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 236 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Beispiel 4.14. Berechnung der Primzahlen bis 100: Beispiel 4.15. ziffer(Z) :- member(Z, [0,1,2,3,4,5,6,7,8,9]). zahl(Zahl) :- ziffer(Z), ziffer(E), Zahl is 10 * Z + E. ?- not( vater( theo, peter ) ). Yes ?- not( vater( peter, lars ) ). No ?- not( vater( peter, X ) ). No keineprimzahl(Z) :- zahl(T), echterTeiler(T, Z). echterTeiler(T,Z) :- T > 1, T < Z, 0 is Z mod T. primzahl(Z) :- keineprimzahl(Z),!,fail. primzahl(Z) :- Z > 1. • Die letzte Anfrage hat die Bedeutung: Ist Peter kein Vater? Anfrage: ¬∃ X V ater(peter, X) ?- zahl( X ), primzahl( X ). X = 2 ; X = 3 ; ... • Sie hat nicht die Bedeutung: Für welche X gilt, daß Peter nicht der Vater von X ist. ∃ X ¬V ater(peter, X) 235 237 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Programmiertechniken in Prolog Man bezeichnet dieses Prinzip der Negation als Negation as failure. ☞ Eine negierte Anfrage in Prolog ist wahr, wenn die Anfrage nicht bewiesen werden kann. • Man beachte, daß not/1 keine Variablenbelegung liefert. • Es entspricht somit nicht der logischen Negation im Sinne der Negation eines Prädikats. • not/1 sollte nur verwendet werden, wenn in dem negierten Pr ädikat keine ungebundenen Variablen mehr auftauchen. • Die Kurzschreibweise für not ist \+. Wir betrachten zunächst die Verarbeitung von Listen. Test auf Exsistenz: Wir wollen prüfen, ob eine Kollektion von Objekten mindestens ein Objekt enthält, das eine gewisse Eigenschaft hat. existence_test( Info, [H|T] ) :- has_property( Info, H ). existence_test( Info, [H|T] ) :- existence_test( Info, T ). Info steht hierbei für eine beliebige Anzahl an Argumenten, die ben ötigt werden, um die gewünschte Eigenschaft zu prüfen. Beispiel 4.17. Wir möchten prüfen, ob eine Liste mindestens eine weitere Liste als Element enthält. ✎ 238 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 240 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Beispiel 4.16. Das Beispiel für die Berechnung der Primzahlen mit direkter Verwendung von not/1 bzw. \+. Test für alle Listenelemente: Wir wollen prüfen, ob alle Elemente einer Liste eine Eigenschaft haben (erfüllen). ziffer(Z) :- member(Z, [0,1,2,3,4,5,6,7,8,9]). zahl(Zahl) :- ziffer(Z), ziffer(E), Zahl is 10 * Z + E. forall_test( Info, [] ). forall_test( Info, [H|T] ) :- has_property( Info, H ), forall_test( Info, T ). keineprimzahl(Z) :- zahl(T), echterTeiler(T, Z). echterTeiler(T,Z) :- T > 1, T < Z, 0 is Z mod T. Beispiel 4.18. Wir möchten prüfen, ob alle Elemente einer Liste Ziffern sind. ✎ primzahl(Z) :- Z > 1, \+ keineprimzahl(Z). 239 241 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Rückgabe eines Resultatswertes (1): Die vorangegangenen Pr ädikate lieferten kein Resultat. In Prolog kann ein Resultat nur über eine freie Variable, die an ein Pr ädikat übergeben wird, geliefert werden. Wir wollen eine Resultat liefern, sobald ein Listenelement eine gewisse Eigenschaft hat. return_if_property( Info, [H|T], Result ) :has_property( Info, H ), result( Info, H, T, Result ). return_if_property( Info, [H|T], Result ) :return_if_property( Info, T, Result ). Beispiel 4.19. Ein Prädikat soll zu einer Liste die Subliste ab einem Element mit dem Wert X liefern. ✎ 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Allgemeine Programmierschemata in Prolog Generate and Test: • Backtracking wird genutzt, um mögliche Lösungen zu erzeugen • Anschließend wird die mögliche Lösung getestet. • Erfüllt die mögliche Lösung nicht die Bedingungen für eine Lösung wird durch Backtracking eine neue Lösung generiert. generate_and_test( Info, X ) :... generate( Info, X ), test( Info, X ), ... 242 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 244 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Rückgabe eines Resultatswertes (2): Wir wollen ein Resultat nach der Verarbeitung aller Listenelemete liefern. Ein Generator zur Implementierung des Prädikats generate kann endlich oder unendlich sein. Ein endlicher Generator wurde in dem Primzahlbeispiel verwendet: process_all( Info, [H1|T1], Result ) :process_one( Info, H1, H2 ), process_all( Info, T1, T2 ), Result = [H2|T2]. ziffer(Z) :- member(Z, [0,1,2,3,4,5,6,7,8,9]). zahl(Zahl) :- ziffer(Z), ziffer(E), Zahl is 10 * Z + E. Er liefert durch Backtracking nur endlich viele Werte. Beispiel 4.20. Wir wollen zu einer Liste von Zahlen eine Liste der Quadrate diese Zahlen bestimmen. ✎ Ein unendlicher Generator für natürliche Zahlen könnte so aussehen: int( 1 ). int( N ) :- int( N1 ), N is N1 + 1. Problem: Durch das Backtracking bricht der Prozeß der Generierung niemals ab. 243 245 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog 4. Logikprogrammierung und Prolog Dies ist aber kein Problem, wenn man nur eine Lösung haben möchte. Man unterdrückt das Backtrackig dann mit dem Cut: generate_and_test( Info, X ) :... generate( Info, X ), test( Info, X ),!, ... Steuerung der Abarbeitung in Prolog Wichtige Prolog-Prädikate • true/0 ist immer wahr. • repeat/0 zur Programmierung von Iterationen Dieses Prädikat entspricht der Definition: repeat. repeat:- repeat. Beispiel 4.21. Berechnung des KGV verschiedener Zahlen. ✎ • call/1 ruft den Interpreter des Prolog-Systems auf. Das Argument von call ist kein Term sondern eine Prolog-Zielklausel. • nl/0 schreibt ein Newline auf die Standardausgabe. • write/1 schreibt das Argument auf die Standardausgabe. • read/1 liest ein Argument von der Standardeingabe und führt dabei eine Unifikation durch. 246 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog Failure-Driven Loop: • Manchmal möchte man Prädikate definieren, die gewisse Dinge tun, wobei das Prädikat aber immer erfüllt sein soll. • Beispiel: Gebe alle elternteil-Beziehungen aus. • Im Vordergund stehen hier die Seiteneffekte (z.B. die Ausgabe). • Vorsicht: Dies ist eine sehr prozedural orientierte Programmiertechnik! 248 4. Logikprogrammierung und Prolog Steuerung der Abarbeitung in Prolog • assert/1 fügt eine Klausel dem Prolog-Programm hinzu. Die Varianten asserta/1 bzw. assertz/1 fügen die Klausel am Anfang bzw. Ende des Prolog-Programms ein. • retract/1 entfernt eine Klausel aus dem Prolog-Programm. failure_driven_loop( Info ) :- generate( Info, Term ), side_effect( Term ), fail. failure_driven_loop( Info ). Beispiel 4.22. Ausgabe aller elternteil-Beziehungen. ✎ 247 249