Procedurellt potpurri Dagens samtalsämnen –Klipp (Cut) –If-then-else –fail/0 –repeat/0 Att läsa –The Art of Prolog, kapitel 11 –Relevant avsnitt i Learn Prolog Now!
Klipp (Cut) Hur ser det ut? –!/0 –En inbyggd procedur som alltid lyckas Vad gör det? –Förhindrar backtracking –‘klipper i sökträdet’ Motivering –Effektivare program –Ökad uttryckskraft (Negation as failure) Problem –Klipp kan förstöra den deklarativa läsningen av ett program och därmed göra dem svårare att förstå!
Klipp – Ett enkelt exempel p :- q, !. p :- r. q :- s. q :- t. s. |?- p. Klippet gör att varken r/0 eller t/0 kommer att prövas. Hade anropet av q/0 misslyckats hade de däremot prövats.
Gröna och röda klipp Grönt klipp: –deklarativ läsning intakt –Varje klausul i ett predikat ’står för sig själv’ Rött klipp: –förstör den deklarativa läsningen
Exempel – max/3 Deklarativ formulering. Två sanna påståenden. Ömsesidigt uteslutande fall. max(A,B,A) :- A >= B. max(A,B,B) :- B > A. Problem –Lämnar en onödig valpunkt (choice point) efter sig.
Exempel – max/3 En fortfarande deklarativ men något effektivare version: max(A,B,A) :- A >= B, !. max(A,B,B) :- B > A. Problem –En onödig beräkning av B>A genomförs.
Exempel – max/3 OK, men vad sägs om följande? max(A,B,A) :- A >= B, !. max(A,B,B). Problem –Rött klipp. Ej deklarativt. Klausuler kan inte läsas som påståenden oberoende av varandra. –Men vad värre är: Programmet är inkorrekt!! Prova t.ex. med följande mål, som ska misslyckas, men som inte gör det: |?- max(4,1,1). yes
Exempel – max/3 Korrekt version: max(A,B,C) :- A >= B, !, C = A. max(A,B,B). % B > A |?- max(4,1,1). yes Sensmoral: Bind inte resultatvariabler förrän efter klippet!
Parentes: En liten övning Definiera ett predikat max/4, som är sant omm värdet i det fjärde argumentet är det högsta av värdena i de tre första argumenten.
En vinnande strategi Sätt inte ut något klipp alls i den första versionen av ett program. Satsa på korrekthet i första hand. Låt varje klausul i sig bilda ett sant påstående. Lägg sedan till klipp för att eliminera onödiga ihågkommanden och/eller beräkningar. Så snart du tänker sätta ut ett klipp, tänk så här: Är detta rätt, är detta bra? Finns det något annat sätt att t.ex. representera data, så att jag slipper sätta ut några klipp? Sätt klippen på rätt ställe! Ett klipp skall placeras precis där man vet att det nuvarande valet av klausul är det rätta, varken förr eller senare.
Den rätta inställningen Sparsamhet är en dygd! Ju färre klipp, ju lättare är programmet att läsa och att förstå! Klipp ska betraktas som ett nödvändigt ont! Använd hellre negation och if-then-else
Saker som bör väcka ens misstanke om att allt kanske inte står rätt till... Det finns fler än ett klipp i kroppen på en klausul. Den sista klausulen i ett predikat innehåller ett klipp. Anropet av ett indeterministiskt predikat följs direkt av ett klipp. Resultatvariabel binds före klippet.
Exempel Ibland ser man: foo(X) :- …, member(X,[…]), !, … Förmodligen vill man ha foo(X) :- …, member_check(X,[…]), … Sensemoral: Om ett anrop av ett indeterministisk predikat följs direkt av ett klipp så är något förmodligen fel.
Övning Definiera member_check/2
If-then-else Inbyggt predikat. Bör ofta användas i stället för klipp. max/3: max(A,B,C) :- (A >= B ->C = A ;C = B ).
If-then-else Definieras som (P -> Q ; R) :- call(P), !, call(Q). (P -> Q ; R) :- call(R). Fundera över max(A,B,C) :- (A >= B ->C = A ;C = B ).
If-then-else If-then-else betyder att foo(X) :- test(X), !, do_something(X). foo(X) :- do_something_else(X). Kan skrivas som foo(X) :- (test(X) ->do_something(X) ;do_something_else(X) ).
fail/0 fail/0 är ett inbyggt predikat som alltid är falskt, dvs som alltid misslyckas Används för att tvinga fram backtracking
Negation Negation as failure (to prove) Inbyggt predikat: \+ Kan definieras i termer av call/1, cut, och fail/0, på följande vis: \+(Goal) :- call(Goal), !, fail. \+(_).
Negation \+ fångar inte riktigt ‘klassisk’ logisk negation The closed world assumption I ett anrop av \+ G måste G vara grund. Exempel: p(a). |?- p(X). Yes, X = a. |?- \+ \+ p(X). true, X is not instantiated
repeat/0 Lyckas alltid, och när man backtrackar så lyckas det igen, och igen, och igen... Använd för att implementera repeatloopar, t.ex.: process_file(F) :- see(F), % Open file F repeat, read(T), % Read a term process_term(T), % Process it T == end_of_file, % Loop back if not at end of file !, seen. % Close the file