This is an old revision of the document!


Scopul laboratorului:

  1. TODO
  2. TODO

Cut

Pentru a demonstra goal-urile necesare, Prolog folosește backtracking pentru a explora toate posibilitățile, rezultând într-un arbore de căutare. Uneori asta duce la comportamente neintuitive:

elem(X, [X|_]).
elem(X, [_|Ys]) :- member(X, Ys).
?- elem(1, [1]).
true ;
false.

?-

Putem porni un trace în program pentru a vedea ce se întâmplă:

?- trace.

[trace]  ?- elem(1, [1]).
   Call: (8) elem(1, [1]) ? creep
   Exit: (8) elem(1, [1]) ? creep
true ;
   Redo: (8) elem(1, [1]) ? creep
   Call: (9) elem(1, []) ? creep
   Fail: (9) elem(1, []) ? creep
   Fail: (8) elem(1, [1]) ? creep
false.

[trace]  ?-

După ce obținem un răspuns positiv din prima clauză, Prolog încearcă să găsească și alte soluții, folosind a doua ramură. Ajunge astfel la goal-ul elem(1, []) care nu poate fi satisfăcut, deci căutarea eșuează. Am vrea să avem control asupra execuției procedurale, eliminând ramuri din arborele de căutare (în cazul nostru, am vrea ca după ce am aflat că un element se găsește în listă, să ne oprim).

Pentru asta avem predicatul !/0 (numit “cut”). Acesta poate fi mereu satisfăcut, dar are un efect lateral: împiedică resatisfacerea părții din stânga, inclusiv clauza (amintiți-vă că o clauză este, practic o “linie”, iar un predicat e o colecție de una sau mai multe clauze. Predicatul elem are două clauze). Astfel, putem rezolva problema noastră:

elem(X, [X|_]) :- !.
elem(X, [_|Ys]) :- member(X, Ys).
[trace]  ?- elem(1, [1]).
   Call: (8) elem(1, [1]) ? creep
   Exit: (8) elem(1, [1]) ? creep
true.

[trace]  ?-

Când primește goal-ul elem(1, [1]), prolog încearcă, de sus în jos, toate clauzele predicatului elem. Pentru că reușește unificarea pe prima clauză (cu substituția X = 1), alege prima clauză. Aceasta este o regulă, deci trebuie încercată satisfacerea corpului acesteia, adică !. Predicatul ! reușește și deci și clauza curentă, iar prolog ne raportează true; dar acum nu mai poate să încerce o altă clauză a predicatului elem.

Green cut

Red cut

Negation as failure

Colectare rezultate

Fie următoarea bază de cunoștiințe genealogice:

% File: norse.pl
father(buri, borr).
father(borr, odin).
father(borr, vili).
father(borr, ve).
father(odin, thor).
father(odin, baldr).
 
mother(bestla, odin).
mother(bestla, vili).
mother(bestla, ve).
mother(jorth, thor).
mother(frigg, baldr).

Putem defini ușor un predicat pentru a determina relația părinte-copil:

parent(P, C) :- mother(P, C).
parent(P, C) :- father(P, C).

După cum ați observat până acum, predicatul definit e versatil; poate fi folosit pentru a verifica o relație, pentru a determina părinții unei persoane, pentru a determina copiii unei persoane, sau pentru a genera toate perechile părinte-copil din baza de cunoștiințe:

?- parent(odin, thor).
true.

?- parent(buri, odin).
false.

?- parent(borr, C).
C = odin ;
C = vili ;
C = ve.

?-

Pentru ultima interogare, există mai multe posibilități de unificare a variabilei C, iar prolog ni le oferă pe rând (introducând ; interactiv, primim următoarea soluție). Uneori, este util să putem colecta toate aceste soluții într-o listă pe care putem, ulterior, face alte procesări. În continuare, vom studia trei predicate pentru a realiza acest lucru.

findall

findall/3 este un predicat care primește un tipar de rezultate, un goal de demonstrat și o listă de ieșire care va conține toate rezultatele construite pe baza tiparului, pentru care goal-ul primit este adevărat. Cel mai simplu tipar util este o singură variabilă, care se regăsește în scopul primit.

?- findall(C, parent(borr, C), L).
L = [odin, vili, ve].

?-

Dacă nu există niciun rezultat, findall/3 generează o listă vidă:

?- findall(C, parent(baldr, C), L).
L = [].

?-

Deasemenea primul argument poate fi un termen compus:

?- findall(child(C, borr), parent(borr, C), L).
L = [child(odin, borr), child(vili, borr), child(ve, borr)].

?-

bagof

bagof/3 este un predicat asemănător cu findall/3, având aceeași semnificație a parametrilor. Diferența dintre cele două este vizibilă atunci când introducem în goal o variabilă care nu apare în template.

?- findall(C, parent(P, C), L).
L = [odin, vili, ve, thor, baldr, borr, odin, vili, ve|...].

?-

Observăm că findall/3 ne întoarce o singură listă cu toate rezultatele posibile. În unele cazuri, ar fi util ca, pentru fiecare părinte P, să primim o altă listă. Pentru asta folosim bagof:

?- bagof(C, parent(P, C), L).
P = bestla,
L = [odin, vili, ve] ;
P = borr,
L = [odin, vili, ve] ;
P = buri,
L = [borr] ;
P = frigg,
L = [baldr] ;
P = jorth,
L = [thor] ;
P = odin,
L = [thor, baldr].

?-

Rezultatele sunt generate în stilul clasic, necesitând ; interactiv. Putem apoi să le colectăm folosind din nou bagof/3:

?- bagof(children(P, L), bagof(C, parent(P, C), L), R).
R = [children(bestla, [odin, vili, ve]), children(borr, [odin, vili, ve]), children(buri, [borr]), children(frigg, [baldr]), children(jorth, [thor]), children(odin, [thor, baldr])].

?-
În ultima interogare, nu contează dacă predicatul folosit în exterior este bagof/3 sau findall/3. Goal-ul de demonstrat (bagof(C, parent(P, C), L)) nu conține variabile “din afară”.

Ca urmare a felului de funcționare, o diferență notabilă între findall/3 și bagof/3 este că, atunci când goal-ul nu poate fi satisfăcut, findall/3 reușește, generând o listă vidă, iar bagof/3 eșuează.

?- findall(C, parent(thor, C), L).
L = [].

?- bagof(C, parent(thor, C), L).
false.

?-

setof

Fie următorul predicat, pentru a stabili care două persoane au un copil în comun:

common_child(F, M) :- father(F, C), mother(M, C).

Am vrea să generăm o listă cu toate persoanele care au un copil cu Odin. Ne folosim de predicatele bagof/3 și common_child/2:

?- bagof(M, common_child(odin, M), L).
L = [jorth, frigg].

?-

Obținem rezultatul dorit. Ce se întâmplă însă dacă două persoane au în comun mai mulți copii?

?- bagof(M, common_child(borr, M), L).
L = [bestla, bestla, bestla].

?-

Rezultatul nu este ideal, deoarece conține duplicate.

setof/3 este un predicat asemănător cu bagof, având aceeași semnificație a parametrilor, dar care generează o listă sortată, fără duplicate (mai multe informații despre ce implică sortarea, puteți găsi aici).

?- setof(M, common_child(odin, M), L).
L = [frigg, jorth].

?- setof(M, common_child(borr, M), L).
L = [bestla].

?-

În rest, setof/3 se comportă ca bagof/3; pentru variabilele din goal care nu apar în template generează soluții separate și eșuează dacă nu există niciuna:

?- setof(M, common_child(F, M), L).
F = borr,
L = [bestla] ;
F = odin,
L = [frigg, jorth].

?- setof(M, common_child(thor, M), L).
false.

?-

Exerciții