SlideShare a Scribd company logo
1 of 145
Download to read offline
A. M. Florea B. Dorohonceanu C. Frâncu
Programare în Prolog
pentru
Inteligenţă Artificială
UNIVERSITATEA “POLITEHNICA” BUCUREŞTI
1997
2
Partea I
Limbajul Prolog
Începutul programării logice poate fi atribuit lui R. Kowalski şi A. Colmerauer şi se situează
la începutul anilor '70. Kowalski a plecat de la o formulă logică de tipul:
S1 ∧ S2 ∧ ... Sn → S
care are, în logica cu predicate de ordinul întâi semnificaţia declarativă conform căreia
S1 ∧ S2 ∧ ... Sn implică S, adică dacă S1 şi S2 ... şi Sn sunt fiecare adevărate atunci şi S este
adevărat, şi a propus o interpretare procedurală asociată. Conform acestei interpretări,
formula de mai sus poate fi scrisă sub forma:
S dacă S1 şi S2 ... şi Sn
şi poate fi executată ca o procedură a unui limbaj de programare recursiv, unde S este
antetul procedurii şi S1, S2, ... Sn corpul acesteia. Deci, pe lângă interpretarea declarativă,
logică, a unei astfel de formule, formula poate fi interpretată procedural astfel: pentru a
executa S se execută S1 şi S2 ... şi Sn.
În aceeaşi perioadă, A. Colmerauer şi colectivul lui de cercetare de la Universitatea din
Marsilia au dezvoltat un limbaj de implementare a acestei abordări, pe care l-au denumit
Prolog, abreviere de la "Programmation et Logique". De atunci şi până în prezent, limbajul
Prolog s-a impus ca cel mai important limbaj de programare logică şi s-au dezvoltat
numeroase implementări, atât ale unor interpretoare cât şi ale unor compilatoare ale
limbajului.
Limbajul Prolog este un limbaj declarativ susţinut de o componentă procedurală. Spre
deosebire de limbajele procedurale, cum ar fi C sau Pascal, în care rezolvarea problemei
este specificată printr-o serie de paşi de execuţie sau acţiuni, într-un limbaj declarativ
problema este specificată prin descrierea universului problemei şi a relaţiilor sau funcţiilor
existente între obiecte din acest univers. Exemple de astfel de limbaje sunt cele funcţionale,
de exemplu Lisp, Scheme, ML, şi cele logice, de exemplu Prolog.
Deşi iniţial a fost gândit pentru un set restrâns de probleme, Prolog a devenit cu timpul
un limbaj de uz general, fiind o unealtă importantă în aplicaţiile de inteligenţă artificială
[CM84, Bra88, SS86]. Pentru multe probleme, un program Prolog are cam de 10 ori mai
puţine linii decât echivalentul lui în Pascal.
În 1983, cercetătorii din Japonia au publicat un plan ambiţios de creare a unor
calculatoare de generaţia a 5-a pentru care Prolog era limbajul de asamblare. Planul nu a
3
reuşit, dar acest proiect a marcat o dezvoltare deosebită a interpretoarelor şi compilatoarelor
de Prolog, precum şi o creştere mare a numărului de programatori în acest limbaj.
Multe clase de probleme poate fi rezolvate în Prolog, existând anumite categorii care
sunt rezolvabile mult mai uşor în Prolog decât în orice alt limbaj procedural. Astfel de
probleme sunt în principal cele dedicate prelucrării simbolice sau care necesită un proces de
căutare a soluţiei într-un spaţiu posibil de transformări ale problemei.
Prezentarea limbajului Prolog ce urmează este în principal orientată pe descrierea
limbajului Prolog standard. Exemplele de programe din această parte cât şi din partea a
doua sunt rulate utilizând interpretorul ARITY Prolog pe microcalculatoare de tip IBM/PC,
mediul de programare ARITY Prolog şi particularităţile lui sintactice fiind prezentate în
partea a doua.
1 Entităţile limbajului Prolog
Limbajul Prolog este un limbaj logic, descriptiv, care permite specificarea problemei de
rezolvat în termenii unor fapte cunoscute despre obiectele universului problemei şi a
relaţiilor existente între aceste obiecte. Execuţia unui program Prolog constă în deducerea
implicaţiilor acestor fapte şi relaţii, programul definind astfel o mulţime de consecinţe ce
reprezintă înţelesul sau semnificaţia declarativă a programului.
Un program Prolog conţine următoarele entităţi:
• fapte despre obiecte şi relaţiile existente între aceste obiecte;
• reguli despre obiecte şi relaţiile dintre ele, care permit deducerea (inferarea) de noi
fapte pe baza celor cunoscute;
• întrebări, numite şi scopuri, despre obiecte şi relaţiile dintre ele, la care programul
răspunde pe baza faptelor şi regulilor existente.
1.1 Fapte
Faptele sunt predicate de ordinul întâi de aritate n considerate adevărate. Ele stabilesc relaţii
între obiectele universului problemei. Numărul de argumente ale faptelor este dat de aritatea
(numărul de argumente) corespunzătoare a predicatelor.
Exemple:
4
Interpretarea particulară a predicatului şi a argumentelor acestuia depinde de
programator. Ordinea argumentelor, odată fixată, este importantă şi trebuie păstrată la orice
altă utilizare a faptului, cu aceeaşi semnificaţie. Mulţimea faptelor unui program Prolog
formează baza de cunoştinţe Prolog. Se va vedea mai târziu că în baza de cunoştinte a unui
program Prolog sunt incluse şi regulile Prolog.
1.2 Scopuri
Obţinerea consecinţelor sau a rezultatului unui program Prolog se face prin fixarea unor
scopuri care pot fi adevărate sau false, în funcţie de conţinutul bazei de cunoştinţe Prolog.
Scopurile sunt predicate pentru care se doreşte aflarea valorii de adevăr în contextul faptelor
existente în baza de cunoştinţe. Cum scopurile pot fi văzute ca întrebări, rezultatul unui
program Prolog este răspunsul la o întrebare (sau la o conjuncţie de întrebări). Acest răspuns
poate fi afirmativ, yes, sau negativ, no. Se va vedea mai târziu că programul Prolog, în cazul
unui răspuns afirmativ la o întrebare, poate furniza şi alte informaţii din baza de cunoştinţe.
Exemplu
Considerând baza de cunoştinţe specificată anterior, se pot pune diverse întrebări, cum
ar fi:
?- iubeste(mihai, maria).
yes deoarece acest fapt există în baza de cunoştinţe
?- papagal(coco).
yes
?- papagal(mihai).
no deoarece acest fapt nu există în baza de cunoştinţe
?- inalt(gelu).
no
1.3 Variabile
În exemplele prezentate până acum, argumentele faptelor şi întrebărilor au fost obiecte
particulare, numite şi constante sau atomi simbolici. Predicatele Prolog, ca orice predicate în
logica cu predicate de ordinul I, admit ca argumente şi obiecte generice numite variabile. În
Prolog, prin convenţie, numele argumentelor variabile începe cu literă iar numele
constantelor simbolice începe cu literă mică. O variabilă poate fi instanţiată (legată) dacă
Fapt: Aritate:
papagal(coco). 1
iubeşte(mihai, maria). 2
iubeşte(mihai, ana). 2
frumoasă(ana). 1
bun(gelu). 1
deplasează(cub, camera1, camera2). 3
5
există un obiect asociat acestei variabile, sau neinstanţiată (liberă) dacă nu se ştie încă ce
obiect va desemna variabila.
La fixarea unui scop Prolog care conţine variabile, acestea sunt neinstanţiate iar
sistemul încearcă satisfacerea acestui scop căutând printre faptele din baza de cunoştinţe un
fapt care poate identifica cu scopul, printr-o instanţiere adecvată a variabilelor din scopul
dat. Este vorba de fapt de un proces de unificare a predicatului scop cu unul din predicatele
fapte existente în baza de cunoştinţe.
La încercarea de satisfacere a scopului, căutarea se face întotdeauna pornind de la
începutul bazei de cunoştinţe. Dacă se întâlneşte un fapt cu un simbol predicativ identic cu
cel al scopului, variabilele din scop se instanţiază conform algoritmului de unificare şi
valorile variabilelor astfel obţinute sunt afişate ca răspuns la satisfacerea acestui scop.
Exemple:
?- papagal(CineEste).
CineEste = coco
?- deplaseaza(Ce, DeUnde, Unde).
Ce = cub, DeUnde = camera1, Unde = camera2
?- deplaseaza(Ce, Aici, Aici).
no
Cum se comportă sistemul Prolog în cazul în care există mai multe fapte în baza de
cunoştinţe care unifică cu întrebarea pusă? În acest caz există mai multe răspunsuri la
întrebare, corespunzând mai multor soluţii ale scopului fixat. Prima soluţie este dată de
prima unificare şi există atâtea soluţii câte unificări diferite există. La realizarea primei
unificări se marchează faptul care a unificat şi care reprezintă prima soluţie. La obţinerea
următoarei soluţii, căutarea este reluată de la marcaj în jos în baza de cunoştinţe. Obţinerea
primei soluţii este de obicei numită satisfacerea scopului iar obţinerea altor soluţii,
resatisfacerea scopului. La satisfacera unui scop căutarea se face întotdeauna de la
începutul bazei de cunoştinţe. La resatisfacerea unui scop, căutarea se face începând de la
marcajul stabilit de satisfacerea anterioară a acelui scop.
Sistemul Prolog, fiind un sistem interactiv, permite utilizatorului obţinerea fie a
primului răspuns, fie a tuturor răspunsurilor. În cazul în care, după afişarea tuturor
răspunsurilor, un scop nu mai poate fi resatisfăcut, sistemul răspunde no.
Exemple:
?- iubeste(mihai, X).
X = maria; tastând caracterul “;” şi Enter, cerem o nouă soluţie
X = ana;
no
?- iubeste(Cine, PeCine).
6
Cine = mihai, PeCine = maria;
Cine = mihai, PeCine = ana;
no
Există deci două soluţii pentru scopul iubeste(mihai, X) şi tot două soluţii pentru
scopul iubeste(Cine, PeCine), considerând tot baza de cunoştinţe prezentată în secţiunea
1.1.
1.4 Reguli
O regulă Prolog exprimă un fapt care depinde de alte fapte şi este de forma:
S :- S1, S2, …Sn.
cu semnificaţia prezentată la începutul acestui capitol. Fiecare Si, i = 1,n şi S au forma
faptelor Prolog, deci sunt predicate, cu argumente constante, variabile sau structuri. Faptul S
care defineşte regula, se numeşte antet de regulă, iar S1, S2, …Sn formează corpul regulii şi
reprezintă conjuncţia de scopuri care trebuie satisfăcute pentru ca antetul regulii să fie
satisfăcut.
Fie următoarea bază de cunoştinţe Prolog:
frumoasa(ana). % 1
bun(vlad). % 2
cunoaste(vlad, maria). % 3
cunoaste(vlad, ana). % 4
iubeste(mihai, maria). % 5
iubeste(X, Y) :- % 6
bun(X),
cunoaste(X, Y),
frumoasa(Y).
Se observă că enunţul (6) defineşte o regulă Prolog; relaţia iubeste(Cine, PeCine),
fiind definită atât printr-un fapt (5) cât şi printr-o regulă (6).
În condiţiile existenţei regulilor în baza de cunoştinţe Prolog, satisfacerea unui scop se
face printr-un procedeu similar cu cel prezentat în Secţiunea 1.2, dar unificarea scopului se
încearcă atât cu fapte din baza de cunoştinţe, cât şi cu antetul regulilor din bază. La
unificarea unui scop cu antetul unei reguli, pentru a putea satisface acest scop trebuie
satisfăcută regula. Aceasta revine la a satisface toate faptele din corpul regulii, deci
conjuncţia de scopuri. Scopurile din corpul regulii devin subscopuri a căror satisfacere se va
încerca printr-un mecanism similar cu cel al satisfacerii scopului iniţial.
Pentru baza de cunoştinţe descrisă mai sus, satisfacerea scopului
?- iubeste(vlad, ana).
7
se va face în următorul mod. Scopul unifică cu antetul regulii (6) şi duce la instanţierea
variabilelor din regula (6): X = vlad şi Y = ana. Pentru ca acest scop să fie îndeplinit,
trebuie îndeplinită regula, deci fiecare subscop din corpul acesteia. Aceasta revine la
îndeplinirea scopurilor bun(vlad), care reuşeşte prin unificare cu faptul (2),
cunoaste(vlad, ana), care reuşeşte prin unificare cu faptul (4), şi a scopului
frumoasa(ana), care reuşeşte prin unificare cu faptul (1). În consecinţă, regula a fost
îndeplinită, deci şi întrebarea iniţială este adevarată, iar sistemul răspunde yes.
Ce se întîmplă dacă se pune întrebarea:
?- iubeste(X, Y).
Prima soluţie a acestui scop este dată de unificarea cu faptul (5), iar răspunsul este:
X = mihai, Y = maria
Sistemul Prolog va pune un marcaj în dreptul faptului (5) care a satisfăcut scopul.
Următoarea soluţie a scopului iubeste(X, Y) se obţine începând căutarea de la acest marcaj
în continuare în baza de cunoştinţe. Scopul unifică cu antetul regulii (6) şi se vor fixa trei
noi subscopuri de îndeplinit, bun(X), cunoaste(X, Y) şi frumoasa(Y). Scopul bun(X) este
satisfăcut de faptul (2) şi variabila X este instanţiată cu valoarea vlad, X = vlad. Se încearcă
acum satisfacerea scopului cunoaste(vlad, Y), care este satisfăcut de faptul (3) şi determină
instanţierea Y = maria. Se introduce în baza de cunoştinţe un marcaj asociat scopului
cunoaste(vlad, Y), care a fost satisfăcut de faptul (3). Se încearcă apoi satisfacerea scopului
frumoasa(maria). Acesta eşuează. În acest moment sistemul intră într-un proces de
backtracking în care se încearcă resatisfacerea scopului anterior satisfăcut,
cunoaste(vlad, Y), în speranţa că o noua soluţie a acestui scop va putea satisface şi scopul
curent care a eşuat, frumoasa(Y). Resatisfacerea scopului cunoaste(vlad, Y) se face
pornind căutarea de la marcajul asociat scopului în jos, deci de la faptul (3) în jos. O nouă
soluţie (resatisfacere) a scopului cunoaste(vlad, Y) este dată de faptul (4) care determină
instanţierea Y = ana. În acest moment se încearcă satisfacerea scopului frumoasa(ana).
Cum este vorba de un nou scop, căutarea se face de la începutul bazei de cunoştinţe şi
scopul frumoasa(ana) este satisfăcut de faptul (1). În consecinţă a doua soluţie a scopului
iubeste(X, Y) este obţinută şi sistemul răspunde:
X = vlad, Y = ana
urmând un mecanism de backtracking, descris intuitiv în figura 1 prin prezentarea arborilor
de deducţie construiţi de sistemul Prolog.
La încercarea de resatisfacere a scopului iubeste(X, Y), printr-un mecanism similar, se
observă că nu mai există alte solutii. În concluzie, fiind dată baza de fapte şi reguli Prolog
anterioară, comportarea sistemului Prolog este:
?- iubeste(X, Y).
8
X = mihai, Y = maria;
X = vlad, Y = ana;
no
Figura 1. Mecanismul de satisfacere a scopurilor în Prolog
Observaţii:
• La satisfacerea unei conjuncţii de scopuri în Prolog, se încearcă satisfacerea fiecărui
scop pe rând, de la stânga la dreapta. Prima satisfacere a unui scop determină
plasarea unui marcaj în baza de cunoştinţe în dreptul faptului sau regulii care a
determinat satisfacerea scopului.
• Dacă un scop nu poate fi satisfăcut (eşuează), sistemul Prolog se întoarce şi încearcă
resatisfacerea scopului din stânga, pornind căutarea în baza de cunoştinţe de la
marcaj în jos. Înainte de resatisfacerea unui scop se elimină toate instanţierile de
variabile determinate de ultima satisfacere a acestuia. Dacă cel mai din stânga scop
din conjuncţia de scopuri nu poate fi satisfăcut, întreaga conjuncţie de scopuri
eşuează.
iubeste(X,Y)
%5 iubeste(mihai,maria)
SUCCES
solutie 1: X=mihai, Y=maria
iubeste(X,Y)
bun(X)
%2 bun(vlad)
SUCCES
cunoaste(X,Y)
%3 cunoaste(vlad,maria)
%6 frumoasa(Y)
frumoasa(maria)
%4 cunoaste(vlad,ana)
%1 frumoasa(ana)
solutie 2: X=vlad, Y=ana
cunoaste(vlad,Y)
SUCCES SUCCES
SUCCESINSUCCES
9
• Această comportare a sistemului Prolog în care se încearcă în mod repetat
satisfacerea şi resatisfacerea scopurilor din conjuncţiile de scopuri se numeşte
backtracking.
• În secţiunea 4 se va discuta pe larg structura de control a sistemului Prolog,
mecanismul fundamental de backtracking şi modurile în care se poate modifica
parţial acest mecanism.
1.5 Un program Prolog simplu
Simplitatea şi expresivitatea limbajului Prolog poate fi pusă în evidentă de următorul
exemplu de descriere a unui circuit logic "poartă ŞI". Se consideră poarta ŞI ca fiind
construită prin conectarea unei "porţi ŞI-NU" cu un inversor. Întregul circuit este definit de
relaţia:
poarta_si(Intrare1, Intrare2, Iesire)
pe baza relaţiilor
poarta_si_nu(Intrare1, Intrare2, Iesire)
inversor(Intrare, Iesire).
În figura 2 este prezentată schema porţii ŞI în care se observă că inversorul este
construit dintr-un tranzistor cu sursa conectată la masă şi un rezistor cu un capăt conectat la
alimentare. Poarta tranzistorului este intrarea inversorului, în timp ce celălalt capăt al
rezistorului trebuie conectat la colectorul tranzistorului care formează ieşirea inversorului.
Figura 2. Un circuit logic poartă ŞI
Variabilele comune între predicate sunt utilizate pentru a specifica legăturile comune.
rezistor(alimentare, n1).
rezistor(alimentare, n2).
n1
n2 n3
n4
n5
10
tranzistor(n2, masa, n1).
tranzistor(n2, masa, n1).
tranzistor(n3, n4, n2).
tranzistor(n5, masa, n4).
inversor(Intrare, Iesire) :-
tranzistor(Intrare, masa, Iesire),
rezistor(alimentare, Iesire).
poarta_si_nu(Intrare1, Intrare2, Iesire) :-
tranzistor(Intrare1, X, Iesire),
tranzistor(Intrare2, masa, X),
rezistor(alimentare, Iesire).
poarta_si(Intrare1, Intrare2, Iesire) :-
poarta_si_nu(Intrare1, Intrare2, X),
inversor(X, Iesire).
Pentru întrebarea
?- poarta_si(In1, In2, Iesire).
In1 = n3, In2= n5, Iesire = n1
răspunsul sistemului Prolog confirmă faptul că circuitul descris este o poartă ŞI, identificând
intrările şi ieşirile corespunzătoare.
2 Sintaxa limbajului Prolog
Aşa cum s-a arătat în secţiunea anterioară, un program Prolog este format din fapte, reguli şi
întrebări, acestea fiind construite pe baza predicatelor definite de utilizator sau predefinite.
În orice sistem Prolog există o mulţime de predicate predefinite, unele dintre acestea fiind
predicate standard, iar altele depinzând de implementare. Argumentele predicatelor Prolog,
prin analogie cu logica predicatelor de ordinul I, se numesc termeni, şi pot fi constante,
variabile sau structuri.
2.1 Constante
Constantele definesc obiecte specifice, particulare, sau relaţii particulare. Există două tipuri
de constante: atomi şi numere. Atomii sunt constante simbolice care încep, de obicei, cu o
literă şi pot conţine litere, cifre şi caracterul “_”. Există şi alte caractere ce pot forma atomi
speciali, care au o semnificaţie aparte în limbaj. Atomii pot desemna:
• obiecte constante care sunt argumentele predicatelor, de exemplu atomii mihai şi
maria în faptul iubeste(mihai, maria);
• predicate Prolog, fie cele definite de utilizator, fie cele predefinite în sistem; de
exemplu atomul iubeste în faptul iubeste(mihai, maria);
11
• atomi speciali, de exemplu atomii “:-” şi “?”- ;
• diverse alte reguli de construcţie sintactică a atomilor depind de implementare.
Numerele pot fi întregi sau reale; sintaxa particulară acceptată cât şi domeniile de
definiţie depinzând de implementare. Detalii despre formatul numerelor şi a altor tipuri de
constante din mediul ARITY Prolog sunt descrise în partea a doua.
2.2 Variabile
Variabilele sunt, din punct de vedere sintactic, tot atomi, dar ele au o semnificaţie specială,
aşa cum s-a arătat în Secţiunea 1.3. Spre deosebire de regulile generale admise pentru
construcţia atomilor, numele unei variabile poate începe şi cu simbolul “_”, ceea ce indică o
variabilă anonimă. Utilizarea unei variabile anonime semnifică faptul că nu interesează
valoarea la care se va instanţia acea variabilă.
De exemplu, interogarea ?- iubeste( _, maria). semnifică faptul că se întreabă dacă
există cineva care o iubeşte pe Maria, dar nu interesează cine anume. Limbajul Prolog face
distincţia între litere mari şi litere mici (este case sensitive). Se reaminteşte că, din punctul
de vedere al convenţiei Prolog, numele oricărei variabile trebuie să înceapă fie cu literă
mare, fie cu “_”.
2.3 Structuri
O structură Prolog este un obiect ce desemneaza o colecţie de obiecte corelate logic, care
formează componentele structurii. Un exemplu este structura asociată obiectului carte, care
este formată din componentele: titlu carte, autor, şi an apariţie. Un fapt ce referă relaţia de
posedare a unei cărţi de Prolog de către Mihai poate fi exprimat astfel:
poseda(mihai, carte(prolog, clocksin, 1981)).
unde carte(prolog, clocksin, 1981) este o structură cu numele carte şi cu trei componente:
prolog, clocksin şi 1981. Se admit şi structuri imbricate, de exemplu:
poseda(mihai, carte(prolog, autori(clocksin, mellish), 1981)).
unde predicatul poseda are două argumente: primul argument este constanta mihai, iar cel
de-al doilea este structura carte(prolog ....), cu două componente, a doua componentă fiind
structura autori(clocksin, mellish).
În Prolog, o structură se defineşte prin specificarea:
(1) numelui structurii ( functorul structurii);
(2) elementelor structurii (componentele structurii).
Structurile Prolog pot fi utilizate pentru reprezentarea structurilor de date, de exemplu
liste sau arbori. În Secţiunea 2.6 se vor prezenta structuri Prolog predefinite care permit
12
lucrul cu liste. Un arbore binar poate fi reprezentat în Prolog tot printr-o structură, de
exemplu:
arb(barbu, arb(ada, vid, vid), vid).
reprezintă un arbore binar cu cheia din rădăcina barbu, cu subarborele drept vid şi cu
subarborele stâng format dintr-un singur nod cu cheia ada.
Observaţii:
• Sintaxa structurilor este aceeaşi cu cea a faptelor Prolog. Un predicat Prolog poate fi
văzut ca o structură a cărui functor este numele predicatului, iar argumentele
acestuia reprezintă componentele structurii.
• Considerarea predicatelor Prolog ca structuri prezintă un interes deosebit; atât datele
cât şi programele în Prolog au aceeaşi formă, ceea ce facilitează tratarea uniformă şi
sinteza dinamică de programe. În Secţiunea 4.3 se vor prezenta predicate predefinite
în Prolog care permit sinteza şi execuţia dinamică a programelor Prolog.
2.4 Operatori
Uneori este convenabil să se scrie anumiţi functori (nume de structuri sau predicate) în
formă infixată. Aceasta este o formă sintactică ce măreşte claritatea programului, cum ar fi
cazul operatorilor aritmetici sau al operatorilor relaţionali.
Limbajul Prolog oferă o mulţime de operatori, unii care se regăsesc în aproape orice
implementare, iar alţii care sunt specifici unei versiuni particulare de implementare a
limbajului. În continuare se vor prezenta o parte dintre operatorii din prima categorie.
(1) Operatori aritmetici
Operatorii aritmetici binari, cum ar fi +, -, *, /, pot fi scrişi în Prolog în notaţie infixată; de
exemplu:
1 + 2*(X * Y) / Z
Aceasta sintaxa este de fapt o rescriere infixată a formei prefixate a structurilor echivalente:
+(1, 2) / (*(X, Y), Z)
Este important de reţinut că operatorii aritmetici sunt o rescriere infixată a unor
structuri deoarce valoarea expresiei astfel definită nu este calculată. Evaluarea expresiei se
face la cerere în cazul în care se foloseste operatorul predefinit infixat is, de exemplu:
X is 1 + 2.
va avea ca efect instanţierea variabilei X la valoarea 3.
(2) Operatori relaţionali
13
Operatorii relaţionali sunt predicate predefinite infixate. Un astfel de operator este
operatorul de egalitate =. Predicatul (operatorul) de egalitate funcţionează ca şi cum ar fi
definit prin următorul fapt:
X = X.
iar încercarea de a satisface un scop de tipul X = Y se face prin încercarea de a unifica X cu
Y. Din aceasta cauză, dându-se un scop de tipul X = Y, regulile de decizie care indică dacă
scopul se îndeplineşte sau nu sunt următoarele:
• Dacă X este variabilă neinstanţiată, iar Y este instanţiată la orice obiect Prolog,
atunci scopul reuşeşte. Ca efect lateral, X se va instanţia la aceeaşi valoare cu cea a
lui Y. De exemplu:
?- carte(barbu, poezii) = X.
este un scop care reuşeşte şi X se instanţiază la carte(barbu, poezii).
• Dacă atât X cât şi Y sunt variabile neinstanţiate, scopul X = Y reuşeşte, variabila X
este legată la Y şi reciproc. Aceasta înseamnă că ori de câte ori una dintre cele două
variabile se instanţiază la o anumită valoare, cealaltă variabila se va instanţia la
aceeaşi valoare.
• Atomii şi numerele sunt întotdeauna egali cu ei înşişi. De exemplu, următoarele
scopuri au rezultatul marcat drept comentariu:
mihai = mihai % este satisfăcut
mare = mic % eşuează
102 = 102 % reuşeşte
1010 = 1011 % eşuează
• Două structuri sunt egale dacă au acelaşi functor, acelaşi număr de componente şi
fiecare componentă dintr-o structură este egală cu componenta corespunzătoare din
cealaltă structură. De exemplu, scopul:
autor(barbilian, ciclu(uvedenrode, poezie(riga_crypto))) =
autor(X, ciclu(uvedenrode, poezie(Y)))
este satisfăcut, iar ca efect lateral se fac instanţierile:
X = barbilian şi Y = riga_crypto.
Operatorul de inegalitate = se defineşte ca un predicat opus celui de egalitate. Scopul
X = Y reuşeşte dacă scopul X = Y nu este satisfăcut şi eşuează dacă X = Y reuşeşte. În
plus, există predicatele relaţionale de inegalitate definite prin operatorii infixaţi >, <, =<,
>=, cu semnificaţii evidente.
14
Un operator interesant este =:=. El face numai evaluare aritmetică şi nici o instanţiere.
Exemplu:
?- 1 + 2 =:= 2 + 1.
yes
?- 1 + 2 = 2 + 1.
no
Predicatul = = testează echivalenţa a două variabile. El consideră cele două variabile
egale doar dacă ele sunt deja partajate. X = = Y reuşeşte ori de câte ori X = Y reuşeşte, dar
reciproca este falsă:
?- X = = X.
X=_23 %variabilă neinstanţiată
?- X= =Y.
no
?- X=Y, X= =Y.
X=_23, Y=_23 % X şi Z variabile neinstanţiate partajate
În cele mai multe implementări Prolog există predefinit operatorul de obţinere a
modulului unui număr, mod; scopul
X is 5 mod 3.
reuşeşte şi X este instanţiat la 2.
Comentariile dintr-un program Prolog sunt precedate de caracterul “%”.
Exemple:
1. Presupunând că nu există operatorul predefinit mod, se poate scrie un predicat
Prolog cu efect similar. O definiţie posibilă a predicatului modulo(X, Y, Z), cu
semnificaţia argumentelor Z = X mod Y , presupunând X, Y > 0, este:
% modulo(X, Y, Z)
modulo(X, Y, X) :- X < Y.
modulo(X, Y, Z) :- X >= Y, X1 is X - Y, modulo(X1, Y, Z).
2. Plecând de la predicatul modulo definit anterior, se poate defini predicatul de calcul
al celui mai mare divizor comun al două numere, conform algoritmului lui Euclid,
presupunând X > 0, Y > 0, astfel:
% cmmdc(X, Y, C)
cmmdc(X, 0, X).
cmmdc(X, Y, C) :- modulo(X, Y, Z), cmmdc(Y, Z, C).
La întrebarea
15
?- cmmdc(15, 25, C).
C=5
răspunsul sistemului este corect. În cazul în care se încearcă obţinerea unor noi
soluţii (pentru semnificaţia cmmdc acest lucru este irelevant, dar interesează din
punctul de vedere al funcţionării sistemului Prolog) se observă ca sistemul intră
într-o buclă infinită datorita imposibilităţii resatisfacerii scopului modulo(X, Y, Z)
pentru Y = 0. Dacă la definiţia predicatului modulo se adaugă faptul:
modulo(X, 0, X).
atunci predicatul modulo(X, Y, Z) va genera la fiecare resatisfacere aceeaşi soluţie,
respectiv solutia corectă, la infinit. Cititorul este sfătuit să traseze execuţia
predicatului cmmdc în ambele variante de implementare a predicatului modulo.
3. Calculul ridicării unui număr natural la o putere naturală se poate face definind
următorul predicat:
% expo(N, X, XlaN)
expo( _ , 0, 0).
expo(0, _ , 1).
expo(N, X, Exp) :- N > 0, N1 is N - 1, expo(N1, X, Z), Exp is Z * X.
2.5 Operatori definiţi de utilizator
Limbajul Prolog permite definirea de noi operatori de către programator prin introducerea în
program a unor clauze de formă specială, numite directive. Directivele acţionează ca o
definire de noi operatori ai limbajului în care se specifică numele, precedenţa şi tipul
(infixat, prefixat sau postfixat) operatorului. Se reaminteşte faptul că orice operator Prolog
este de fapt o structură care are ca functor numele operatorului şi ca argumente argumentele
operatorului, dar este scris într-o sintaxă convenabilă. La definirea de noi operatori în
Prolog, se creează de fapt noi structuri cărora le este permisă o sintaxă specială, conform
definiţiei corespunzătoare a operatorului. Din acest motiv, nu se asociază nici o operaţie
operatorilor definiţi de programator. Operatorii noi definiţi sunt utilizaţi ca functori numai
pentru a combina obiecte în structuri şi nu pentru a executa acţiuni asupra datelor, deşi
denumirea de operator ar putea sugera o astfel de acţiune.
De exemplu, în loc de a utiliza structura:
are(coco, pene)
se poate defini un nou operator are
:- op(600, xfx, are).
caz în care este legală expresia
16
coco are pene.
Definirea de noi operatori se face cu ajutorul directivei:
:- op(precedenţă-operator, tip-operator, nume-operator).
Operatorii sunt atomi iar precedenta lor trebuie să fie o valoare întreagă într-un anumit
interval şi corelată cu precedenţa operatorilor predefiniţi în limbaj. Tipul operatorilor
fixează caracterul infixat, prefixat sau postfixat al operatorului şi precedenţa operanzilor săi.
Tipul operatorului se defineşte utilizând una din următoarele forme standard:
(1) operatori infixaţi: xfx xfy yfx
(2) operatori prefixaţi: fx fy
(3) operatori postfixaţi: xf yf
unde f reprezintă operatorul, iar x şi y operanzii săi. Utilizarea simbolului x sau a simbolului
y depinde de precedenţa operandului faţă de operator. Precedenţa operanzilor se defineşte
astfel:
• un argument între paranteze sau un argument nestructurat are precedenţa 0;
• un argument de tip structură are precedenţa egală cu cea a functorului operator.
Observaţie: În ARITY Prolog, cu cât valoare-precedenţă-operator este mai mare, cu atât
operatorul are o precedenţă mai mică şi invers.
Semnificaţiile lui x şi y în stabilirea tipului operatorului sunt următoarele:
• x reprezintă un argument (operand) cu precedenţă strict mai mică decât cea a
functorului (operatorului) f
precedenţa( x ) < precedenţa( f )
• y reprezintă un argument (operand) cu precedenţă mai mică sau egală cu cea a
functorului (operandului) f
precedenţa( y ) ≤ precedenţa( f )
Aceste reguli ajută la eliminarea ambiguităţii operatorilor multipli cu aceeaşi
precedenţă. De exemplu, operatorul predefinit în Prolog - (minus) este definit din punct de
vedere al tipului ca yfx, ceea ce înseamnă că structura a - b -c este interpretată ca (a - b) -c
şi nu ca a -(b -c). Dacă acest operator ar fi fost definit ca xfy, atunci interpretarea structurii
a - b -c ar fi fost a -(b -c).
Numele operatorului poate fi orice atom Prolog care nu este deja definit în Prolog. Se
poate folosi şi o listă de atomi, dacă se definesc mai mulţi operatori cu aceeaşi precedenţă şi
acelaşi tip.
Exemple:
17
:- op(100, xfx, [este, are]).
:- op(100, xf, zboara).
coco are pene.
coco zboara.
coco este papagal.
bozo este pinguin.
?- Cine are pene.
Cine = coco
?- Cine zboara.
Cine = coco
?- Cine este Ce.
Cine = coco, Ce = papagal;
Cine = bozo, Ce = pinguin;
no
În condiţiile în care se adaugă la baza de cunoştinţe anterioară şi definiţia operatorilor
daca, atunci şi si
:- op(870, fx, daca).
:- op(880, xfx, atunci).
:- op(880, xfy, si).
următoarea structură este validă în Prolog:
daca Animalul are pene
şi Animalul zboara
atunci Animalul este pasare.
2.6 Liste
O listă este o structură de date ce reprezintă o secvenţă ordonată de zero sau mai multe
elemente. O listă poate fi definită recursiv astfel:
(1) lista vidă (lista cu 0 elemente) este o listă
(2) o listă este o structură cu două componente: primul element din listă (capul
listei) şi restul listei (lista formată din urmatoarele elemente din lista).
Sfârşitul unei liste este de obicei reprezentat ca lista vidă.
În Prolog structura de listă este reprezentată printr-o structură standard, predefinită, al
cărei functor este caracterul “.” şi are două componente: primul element al listei şi restul
listei. Lista vidă este reprezentată prin atomul special [ ]. De exemplu, o listă cu un singur
element a se reprezintă în Prolog, prin notaţie prefixată astfel:
.(a, [ ]) având sintaxa in ARITY Prolog '.'(a, [ ])
18
iar o listă cu trei elemene, a, b, c, se reprezintă ca:
.(a, . (b, . (c, [ ]))) cu sintaxa în ARITY Prolog '.'(a, '. '(b, '. '(c, [ ]))).
Deoarece structura de listă este foarte des utilizată în Prolog, limbajul oferă o sintaxă
alternativă pentru descrierea listelor, formată din elementele listei separate de virgulă şi
încadrate de paranteze drepte. De exemplu, cele două liste anterioare pot fi exprimate astfel:
[a]
[a, b, c]
Această sintaxă a listelor este generală şi valabilă în orice implementare Prolog. O
operaţie frecventă asupra listelor este obţinerea primului element dintr-o listă şi a restului
listei, deci a celor două componente ale structurii de listă. Aceasta operaţie este realizată în
Prolog de operatorul de scindare a listelor “|” scris sub următoarea formă:
[Prim | Rest]
Variabila Prim stă pe postul primului element din listă, iar variabila Rest pe postul
listei care conţine toate elementele din listă cu excepţia primului. Acest operator poate fi
aplicat pe orice listă care conţine cel puţin un element. Dacă lista conţine exact un element,
Rest va reprezenta lista vidă. Încercarea de identificare a structurii [Prim | Rest] cu o listă
vidă duce la eşec. Mergând mai departe, se pot obţine chiar primele elemente ale listei şi
restul listei. Iată câteva echivalenţe:
[a, b, c] = [a | [b, c] ] = [a, b | [c] ] = [a, b, c | [ ] ] = [a | [b | [c] ] ] = [a | [b | [c | [ ] ] ] ].
În Prolog elementele unei liste pot fi atomi, numere, liste şi în general orice structuri.
În consecinţă se pot construi liste de liste.
Exemple:
1. Se poate defini următoarea structură de listă:
[carte(barbu, poezii), carte(clocksin, prolog)]
2. Considerând următoarele fapte existente în baza de cunoştinţe Prolog
pred([1, 2, 3, 4]).
pred([coco, sta, pe, [masa, alba]]).
se pot pune următoarele întrebări obţinând răspunsurile specificate:
?- pred([Prim | Rest]).
Prim = 1, Rest = [2, 3, 4];
Prim = coco, Rest = [sta, pe, [masa, alba]];
no
?- pred([ _, _ , _ , [ _ | Rest]])
19
Rest = [alba]
3. Un predicat util în multe aplicaţii este cel care testează apartenenţa unui element la
o listă şi care se defineşte astfel:
% membru(Element, Lista)
membru(Element, [Element | _ ]).
membru(Element, [ _ | RestulListei]) :- membru(Element, RestListei).
Funcţionarea acestui scurt program Prolog poate fi urmărită cerând răspunsul
sistemului la următoarele scopuri:
?- membru(b, [a, b, c]). %1
yes
?- membru(X, [a, b, c]). %2
X = a;
X = b;
X = c;
no
?- membru(b, [a, X, b]). %3
X = b;
X = _0038;
no
Deci pentru cazul în care primul argument este o variabilă (%2) există trei soluţii
posibile ale scopului membru(Element, Lista). Dacă lista conţine o variabilă (%3)
există două soluţii pentru forma listei ([a, b, b] sau [a, _ , b]).
4. Un alt predicat util este cel de concatenare a două liste L1 şi L2, rezultatul
concatenării obţinându-se în lista L3, iniţial neinstanţiată.
% conc(L1, L2, L3)
conc([], L2, L2). % lista vidă concatenată cu L2 este L2.
conc([Prim1|Rest1], Lista2, [Prim1|Rest3]) :-
conc(Rest1, Lista2, Rest3).% lista rezultată este primul element
% al sublistei curente din L1 concatenat
% cu lista întoarsă de apelul recursiv.
?- conc([a, b], [c, d, e], L3).
L3 = [a, b, c, d, e];
no
?- conc([a, b], [c, d, e], L3).
L3 = [a, b, c, d, e]. % “.” şi Enter când nu căutăm alte soluţii
yes
?- conc(L1, [c, d, e], [a, b, c, d, e]).
L1 = [a, b];
20
no
?- conc([a, b], L2, [a, b, c, d, e]).
L2 = [c, d, e];
no
?- conc(L1, L2, [a, b]).
L1 = [ ], L2 = [a, b];
L1 = [a], L2 = [b];
L1 = [a, b], L2 = [ ];
no
Se observă că pentru cazul în care predicatul de concatenare are un singur argument
neinstanţiat există o singură soluţie, iar pentru cazul în care primele două argumente
sunt neinstanţiate (variabile) se obţin mai multe soluţii, corespunzătoare tuturor
variantelor de liste care prin concatenare generează cea de a treia listă.
5. Următorul exemplu defineşte predicatul de testare a existenţei unei sortări în ordine
crescătoare a elementelor unei liste de întregi. Predicatul reuşeşte dacă elementele
listei sunt sortate crescător şi eşuează în caz contrar.
% sortcresc(Lista)
sortcresc([ ]). % lista vidă se consideră sortată
sortcresc([ _ ]). % lista cu un singur element este sortată
sortcresc([X, Y | Rest]) :- X = < Y, sortcresc([Y | Rest]).
?- sortcresc([1, 2, 3, 4]).
yes
?- sortcresc([1, 3, 5, 4]).
no
?- sortcresc([ ]).
yes
Dacă se consideră că lista vidă nu este o listă sortată crescător atunci se poate
elimina faptul:
sortcresc([ ]).
din definiţia predicatului sortcresc, comportarea lui pentru liste diferite de lista vidă
rămânând aceeaşi.
Observaţii:
• Exemplul 3 pune în evidenţă o facilitate deosebit de interesantă a limbajului Prolog,
respectiv puterea generativă a limbajului. Predicatul membru poate fi utilizat atât
pentru testarea apartenenţei unui element la o listă, cât şi pentru generarea, pe rând,
21
a elementelor unei liste prin resatisfacere succesivă. În anumite contexte de utilizare
această facilitate poate fi folositoare, iar în altele ea poate genera efecte nedorite
atunci când predicatul membru este utilizat în definirea altor predicate, aşa cum se
va arăta în partea a doua a lucrării.
• Aceeaşi capacitate generativă a limbajului Prolog poate fi observată şi în Exemplul
4 unde în funcţie de combinaţiile de argumente instanţiate şi neinstanţiate,
predicatul conc poate produce rezultate relativ diferite.
• La definirea unui predicat p care va fi utilizat în definirea altor predicate trebuie
întotdeauna să se analizeze numărul de soluţii şi soluţiile posibile ale predicatului p.
Acest lucru este necesar deoarece dacă p apare într-o conjuncţie de scopuri
p1,…, pi-1, p ,pi+1,…, pn şi unul dintre scopurile pi+1,…, pn eşuează, mecanismul de
backtracking din Prolog va încerca resatisfacerea scopului p. Numărul de soluţii şi
soluţiile scopului p influenţează astfel, în mod evident, numărul de soluţii şi
soluţiile conjuncţiei de scopuri p1,…, pi-1, p ,pi+1,…, pn. Exemple în acest sens şi
modalităţi de reducere a numărului total de soluţii ale unui corp vor fi prezentate în
Secţiunea 4.2.
3 Limbajul Prolog şi logica cu predicate de ordinul I
Limbajul Prolog este un limbaj de programare logică. Deşi conceput iniţial pentru
dezvoltarea unui interpretor de limbaj natural, limbajul s-a impus ca o soluţie practică de
construire a unui demonstrator automat de teoreme folosind rezoluţia. Demonstrarea
teoremelor prin metoda rezoluţiei necesită ca axiomele şi teorema să fie exprimate în forma
clauzală, adică o disjuncţie de literali, unde un literal este un predicat sau un predicat negat.
Pentru detalii despre noţiunea de clauză şi modul de transformare a unei formule din logica
cu predicate de ordinul I în formă clauzală se poate consulta [Flo93]. Sintaxa şi semantica
limbajului Prolog permit utilizarea numai a unei anumite forme clauzale a formulelor bine
formate: clauze Horn distincte.
Deoarece faptele şi regulile Prolog sunt în formă clauzală, forma particulară a
clauzelor fiind clauze Horn distincte, ele se mai numesc şi clauze Prolog.
Definiţie. Se numeşte clauză Horn o clauză care conţine cel mult un literal pozitiv. O
clauză Horn poate avea una din următoarele patru forme:
(1) o clauză unitară pozitivă formată dintr-un singur literal pozitiv (literal
nenegat);
(2) o clauză negativă formată numai din literali negaţi;
(3) o clauză formată dintr-un literal pozitiv şi cel puţin un literal negativ, numită şi
clauză Horn mixtă;
22
(4) clauză vidă.
Definiţie. Se numeşte clauză Horn distinctă o clauză care are exact un literal pozitiv, ea
fiind fie o clauză unitară pozitivă, fie o clauză Horn mixtă.
Clauzele Horn unitare pozitive se reprezintă în Prolog prin fapte, iar clauzele Horn
mixte prin reguli. O clauză Horn mixtă de forma:
∼S1 ∨ ∼S2 ∨…∨ ∼Sn ∨ S
se exprimă în Prolog prin regula:
S :- S1, S2, …Sn.
Semnificaţia intuitivă a unei reguli Prolog are un corespondent clar în logica cu
predicate de ordinul I dacă se ţine cont de faptul că o clauză Horn mixtă poate proveni din
următoarea formulă bine formată:
S1 ∧ S2 ∧…∧ Sn → S
Variabilele din clauzele distincte se transformă în variabile Prolog, constantele din
aceste formule în constante Prolog, iar funcţiile pot fi asimilate cu structuri Prolog. Deci
argumentele unui predicat Prolog au forma termenilor din calculul cu predicate de ordinul I.
Exemple:
1. Fie următoarele enunţuri: Orice sportiv este puternic. Oricine este inteligent şi
puternic va reuşi în viaţă. Oricine este puternic va reuşi în viaţă sau va ajunge
bătăuş. Există un sportiv inteligent. Gelu este sportiv. Exprimând enunţurile în
logica cu predicate de ordinul I se obţin următoarele formule bine formate:
A1. (∀x) (sportiv(x) → puternic(x))
A2. (∀x) (inteligent(x) ∧ puternic(x) → reuşeşte(x))
A3. (∀x) (puternic(x) → (reuşeşte(x) ∨ bătăuş(x)))
A4. (∃x) (sportiv(x) ∧ inteligent(x))
A5. Sportiv(gelu)
Axiomele se transformă în forma clauzală şi se obţin următoarele clauze:
C1. ∼ sportiv(x) ∨ puternic(x)
C2. ∼ inteligent(x) ∨ ~ puternic(x) ∨ reuseste(x)
C3. ~ puternic(x) ∨ reuseste(x) ∨ bataus(x)
C4. sportiv(a)
C4'. inteligent(a)
C5. sportiv(gelu)
23
Clauzele C1, C2, C4, C4' şi C5 pot fi transformate în Prolog deoarece sunt clauze
Horn distincte, dar clauza C3 nu poate fi transformată în Prolog. Programul Prolog
care se obţine prin transformarea acestor clauze este următorul:
puternic(X) :- sportiv(X).
reuseste(X) :- inteligent(X), puternic(X).
sportiv(a).
inteligent(a).
sportiv(gelu).
2. Fie următoarele enunţuri:
(a) Orice număr raţional este un număr real.
(b) Există un număr prim.
(c) Pentru fiecare număr x există un număr y astfel încât x < y .
Dacă se notează cu prim(x) “x este număr prim”, cu rational(x) ”x este număr
raţional”, cu real(x) “x este număr real” şi cu mai_mic(x, y) “x este mai mic decât
y“, reprezentarea sub formă de formule bine formate în calculul cu predicate de
ordinul I este următoarea:
A1. (∀x) (raţional(x) → real(x))
A2. (∃x) prim(x)
A3. (∀x) (∃y) mai_mic(x, y)
Reprezentarea în formă clauzală este:
C1. ~ rational(x) ∨ real(x)
C2. prim(a)
C3. mai_mic(x, mai_mare(x))
unde mai_mare(x) este funcţia Skolem care înlocuieşte variabila y cuantificată
existenţial. Forma Prolog echivalentă a acestor clauze este:
real(X) :- rational(X).
prim(a).
mai_mic(X, mai_mare(X)).
unde mai_mare(x) este o structură Prolog.
Este evident că nu orice axiomă poate fi transformată în Prolog şi că, dintr-un anumit
punct de vedere, puterea expresivă a limbajului este inferioară celei a logicii cu predicate de
ordinul I. Pe de altă parte, limbajul Prolog oferă o mulţime de predicate de ordinul II, adică
predicate care acceptă ca argumente alte predicate Prolog, care nu sunt permise în logica cu
predicate de ordinul I. Acest lucru oferă limbajului Prolog o putere de calcul superioară
24
celei din logica clasică. Uneori, aceste predicate de ordinul II existente în Prolog pot fi
folosite pentru a modela versiuni de programe Prolog echivalente cu o mulţime de axiome
care nu au o reprezentare în clauze Horn distincte.
Se propune cititorului, după parcurgerea secţiunii următoare, să revină la Exemplul 1
şi să încerce găsirea unei forme Prolog relativ echivalente (cu un efect similar) cu clauza
C3.
Limbajul Prolog demonstrează scopuri (teoreme) prin metoda respingerii rezolutive
[Flo93]. Strategia rezolutivă utilizată este strategia de intrare liniară, strategie care nu este în
general completă dar este completă pentru clauze Horn. Această strategie este deosebit de
eficientă din punct de vedere al implementării şi jutifică astfel forma restricţionată a
clauzelor Prolog.
4 Structura de control a limbajului Prolog
În această secţiune se prezintă în detaliu structura de control specifică sistemului Prolog.
Spre deosebire de limbajele de programare clasice, în care programul defineşte integral
structura de control şi fluxul de prelucrări de date, în Prolog există un mecanism de control
predefinit.
4.1 Semnificaţia declarativă şi procedurală a programelor Prolog
Semnificaţia declarativă a unui program Prolog se referă la interpretarea strict logică a
clauzelor acelui program, rezultatul programului fiind reprezentat de toate consecinţele
logice ale acestuia. Semnificaţia declarativă determină dacă un scop este adevărat (poate fi
satisfăcut) şi, în acest caz, pentru ce instanţe de variabile este adevărat scopul. Se
reaminteşte că o instanţă a unei clauze este clauza de bază (clauza fără variabile) obţinută
prin instanţierea variabilelor din clauza iniţială. În aceste condiţii semnificaţia declarativă a
unui program Prolog se poate defini precum urmează:
Definiţie. Un scop S este adevărat într-un program Prolog, adică poate fi satisfăcut sau
derivă logic din program, dacă şi numai dacă:
1. există o clauză C a programului;
2. există o instanţă I a clauzei C astfel încât:
2.1. antetul lui I să fie identic cu cel al lui S;
2.2. toate scopurile din corpul lui I sunt adevărate, deci pot fi satisfăcute.
Observaţii:
• În definiţia de mai sus clauzele referă atât fapte cât şi reguli Prolog. Antetul unei
clauze este antetul regulii dacă clauza este o regulă Prolog (clauză Horn mixtă) şi
25
este chiar faptul dacă clauza este un fapt Prolog (clauză unitară pozitivă). Corpul
unui fapt este considerat vid şi un fapt este un scop care se îndeplineşte întotdeauna.
• În cazul în care întrebarea pusă sistemului Prolog este o conjuncţie de scopuri,
definiţia anterioară se aplică fiecărui scop din conjuncţie.
Semnificaţia procedurală a unui program Prolog se referă la modul în care sistemul
încearcă satisfacerea scopurilor, deci la strategia de control utilizată. Diferenţa dintre
semnificaţia declarativă şi semnificaţia procedurală este aceea că cea de a doua defineşte, pe
lânga relaţiile logice specificate de program, şi ordinea de satisfacere a scopurilor şi
subscopurilor. În prima secţiune a acestui capitol s-a făcut o prezentare informală a
modalităţii procedurale de satisfacere a scopurilor în Prolog. În continuare se rafinează
această comportare. Din punct de vedere procedural, un program Prolog poate fi descris de
schema bloc prezentată în figura 3.
Figura 3. Comportarea procedurală a sistemului Prolog
Semnificaţia procedurală a unui program Prolog poate fi descrisă de următorul
algoritm în care L = {S1, S2, …, Sn} este lista de scopuri de satisfăcut, iar B este lista de
instanţieri (unificări) ale variabilelor din scopuri, iniţial vidă. Această listă se va actualiza la
fiecare apel.
Algoritm. Strategia de control Prolog
SATISFACE(L,B)
1. dacă L = {} % lista vidă
atunci afişează B şi întoarce SUCCES.
2. Fie S1 ← primul scop din L şi p predicatul referit de S1. Parcurge clauzele programului,
de la prima clauză sau de la ultimul marcaj fixat, asociat lui p, până ce se găseşte o
clauză C al cărei antet unifică cu S1.
3. dacă nu există o astfel de clauză
atunci întoarce INSUCCES.
4. Fie C de forma H :- D1,…,Dm, m ≥ 0. Plasează un marcaj în dreptul clauzei C, asociat
lui p. (H conţine predicatul p).
5. Redenumeşte variabilele din C şi obtine C' astfel încât să nu existe nici o variabilă
comună între C' şi L; C' este de tot forma H :- D1,…,Dm. cu redenumirile făcute.
6. L’ ← { D1,…,Dm, S2,…,Sn } % dacă C este fapt, atunci L se va reduce
Execuþie
sistem Prolog
conjuncþii
de scopuri
program Prolog
indicator SUCCES/INSUCCES
instanþele variabilelor din scopuri
26
7. Fie B1 instanţierea variabilelor care rezultă din unificarea lui S1 cu H.
8. Substituie variabilele din L’ cu valorile date de B1 şi obţine:
L” ← { D1’,…,Dm’, S2’,…,Sn’ }.
9. B ← B ∪ B1.
10.dacă SATISFACE(L”, B)=SUCCES
atunci afişează B şi întoarce SUCCES.
11.repetă de la 1.
sfârşit
Observaţii:
• Algoritmul de mai sus reprezintă de fapt implementarea strategiei rezolutive de
intrare liniară utilizată de limbajul Prolog, pentru care se impune o ordine
prestabilită de considerare a clauzelor.
• Algoritmul arată funcţionarea sistemului pentru găsirea primei soluţii.
• Indicatorul SUCCES/INSUCCES corespunde răspunsurilor de yes, respectiv no,
date de sistemul Prolog la încercarea de satisfacere a unei liste de scopuri.
Următoarele două exemple pun în evidenţă diferenţa dintre semnificaţia declarativă şi
semnificaţia procedurală a programelor Prolog. Primul exemplu este un scurt program care
defineşte relaţiile de părinte şi strămoş existente între membrii unei familii. Se dau patru
definiţii posibile ale relaţiei de strămoş, str1, str2, str3 şi str4, toate fiind perfect corecte
din punct de vedere logic, deci din punct de vedere a semnificaţiei declarative a limbajului.
% parinte(IndividX, IndividY)
% stramos(IndividX, IndividZ)
parinte(vali, gelu).
parinte(ada, gelu).
parinte(ada, mia).
parinte(gelu, lina).
parinte(gelu, misu).
parinte(misu, roco).
str1(X, Z) :- parinte(X, Z).
str1(X, Z) :- parinte(X, Y), str1(Y, Z).
% Se schimbă ordinea regulilor:
str2(X, Z) :- parinte(X, Y), str2(Y, Z).
str2(X, Z) :- parinte(X, Z).
27
28
% Se schimbă ordinea scopurilor în prima variantă:
str3(X, Z) :- parinte(X, Z).
str3(X, Z) :- str3(X, Y), parinte(Y, Z).
% Se schimbă atât ordinea regulilor, cât şi ordinea scopurilor:
str4(X, Z) :- str4(X, Y), parinte(Y, Z).
str4(X, Z) :- parinte(X,Z).
Figura 4. Arborele genealogic definit de programul Prolog
Figura 4 prezintă arborele genealogic definit de faptele Prolog anterioare. În raport cu
semantica declarativă a limbajului se pot schimba, fără a modifica înţelesul logic, atât
ordinea clauzelor care definesc relaţia de strămoş, cât şi ordinea scopurilor în corpul regulii
de aflare a strămoşilor. Schimbând ordinea clauzelor se obţine din predicatul str1 definiţia
alternativă a relaţiei de strămoş str2; schimbând ordinea scopurilor din corpul regulii în
varianta iniţială se obţine definiţia predicatului str3; şi, schimbând atât ordinea clauzelor,
cât şi ordinea scopurilor în regulă, se obţine predicatul str4.
Comportarea programului folosind cele patru definiţii alternative, dacă se doreşte
aflarea adevărului relaţiei de strămoş pentru perechile (ada, mişu) şi (mia, roco), este cea
prezentată în continuare.
?- str1(ada, misu).
yes
Pentru acest scop, arborele de deducţie este prezentat în figura 5.
vali ada
gelu mia
lina mişu
roco
29
Figura 5. Arborele de deducţie a scopului str1(ada, misu)
?- str2(ada, misu).
yes
?- str3(ada, misu).
yes
Pentru scopul str3(ada, misu), arborele de deducţie este cel prezentat în figura 6.
Figura 6. Arborele de deducţie a scopului str3(ada, misu)
?- str4(ada, misu).
% buclă infinita; eventual mesaj de depăşire a stivei
Pentru acest scop, arborele de deducţie este cel prezentat în figura 7.
str1(ada, misu)
parinte(ada, misu)
INSUCCES
parinte(ada, Y), str1(Y, misu)
parinte(ada, gelu)
Y=gelu
SUCCES
str1(gelu, misu)
parinte(gelu, misu)
SUCCES
str3(ada, misu)
parinte(ada, misu)
INSUCCES
str3(ada, Y), parinte(Y, misu)
parinte(ada, Y')
Y=Y'
SUCCES
parinte(gelu, misu)
SUCCES
parinte(ada, gelu)
Y'=gelu
30
Figura 7. Arborele de deducţie (infinit) a scopului str4(ada, misu)
Din punctul de vedere al semnificaţiei procedurale, cele patru definiţii nu sunt
echivalente. Primele două, str1 şi str2, pot da răspuns pozitiv sau negativ la orice întrebare,
dar str2 este mai ineficientă decât str1. Cititorul poate încerca trasarea arborelui de deducţie
al satisfacerii scopului str2(ada, misu) şi compararea acestuia cu cel corespunzător
satisfacerii scopului str1(ada, misu). Definiţia str4 produce o buclă infinită datorită intrării
infinite în recursivitate. Este evident o definiţie greşită din punct de vedere procedural.
Definiţia relaţiei de strămoş str3 este o definiţie "capcană". După cum se poate observă din
arborele de deducţie prezentat, răspunsul sistemului este afirmativ în cazul în care există o
relaţie de strămoş între cele două persoane argumente ale predicatului. În cazul în care o
astfel de relaţie nu există, sistemul intră într-o buclă infinită, cum ar fi, de exemplu, pentru
persoanele mia şi roco.
?- str1(mia, roco).
no
?- str2(mia, roco).
no
?- str3(mia, roco).
% buclă infinită; eventual mesaj de depăşire a stivei
În acest caz, arborele de deducţie al scopului str3(mia, roco) este prezentat în figura 8.
Datorită semnificaţiei procedurale a limbajului Prolog trebuie urmărit cu atenţie modul de
definire a unui predicat atât din punctul de vedere al ordinii clauzelor, cât şi din punctul de
vedere al ordinii scopurilor în corpul regulilor.
str4(ada, misu)
str4(ada, Y), parinte(Y, misu)
str4(ada, Y'), parinte(Y', Y)
str4(ada, Y"), parinte(Y", Y)
arbore infinit
...
31
Figura 8. Arbore de deducţie infinit pentru un scop fals
Al doilea exemplu se referă la rezolvarea în Prolog a următoarei probleme. O maimuţă
se găseşte la uşa unei camere şi o banană se află agăţată de plafon în centrul camerei. Lângă
fereastra camerei se află o cutie pe care maimuţa o poate folosi pentru a se urca pe ea şi a
ajunge la banană. Maimuţa ştie să facă următoarele mişcări: să meargă pe sol, să se urce pe
cutie, să deplaseze cutia dacă este lângă cutie, să apuce banana dacă este pe cutie şi cutia
este în centrul camerei. Se cere să se scrie un program Prolog care să descrie această
problema şi care să poată răspunde dacă, dintr-o configuraţie iniţială specificată prin poziţia
maimuţei şi a cubului, maimuţa poate apuca banana după execuţia unei secvenţe de mişcări.
Reprezentarea universului problemei în Prolog este specificată după cum urmează.
Starea iniţială este:
(1) Maimuţa este la uşă.
(2) Maimuţa este pe sol.
(3) Cutia este la fereastră.
(4) Maimuţa nu are banana.
şi poate fi descrisă prin structura Prolog:
stare(la_usa, pe_sol, la_fereastra, nu_are_banana)
Starea finală este aceea în care maimuţa are banana
stare( _ , _ , _ , are_banana)
Mişcările cunoscute de maimuţă, deci operatorii de tranziţie dintr-o stare în alta, sunt:
(m1) Apucă banana. apucă
(m2) Urcă pe cutie. urcă
(m3) Mută cutia. mută(Poziţia1, Poziţia2)
str3(mia, roco)
parinte(mia, roco)
INSUCCES
str3(mia, Y), parinte(Y, roco)
parinte(mia, Y)
INSUCCES
str3(mia, Y'), parinte(Y', Y)
parinte(mia, Y')
INSUCCES
str3(mia, Y"), parinte(Y", Y')
parinte(mia, Y")
INSUCCES
...
arbore infinit
32
(m4) Merge (maimuţa se deplasează pe sol). merge(Poziţia1, Poziţia2)
şi sunt reprezentate prin structurile Prolog indicate la dreapta mişcărilor.
Dintr-o anumită stare numai anumite mişcări sunt permise. De exemplu, maimuţa nu
poate apuca banana decât dacă este urcată pe cutie şi cutia este în centrul camerei, adică sub
banană. Mişcările permise pot fi reprezentate în Prolog prin predicatul de deplasare depl cu
trei argumente:
depl(Stare1, Mişcare, Stare2)
care transformă problema din starea Stare1 în starea Stare2 prin efectuarea mişcării legale
Mişcare în starea Stare1. Se observă că reprezentarea aleasă este o reprezentare a
problemei folosind spaţiul stărilor [Flo93].
Soluţia problemei este completată prin adăugarea predicatului poatelua(Stare) care va
reuşi dacă maimuţa poate ajunge din starea iniţială Stare într-o stare finală în care poate lua
banana, stare finală descrisă de:
poate_lua(stare( _ , _ , _ , are_banana)).
Programul Prolog complet este prezentat în continuare.
% Structura stare:
% stare(poz_o_maimuta, poz_v_maimuta, poz_cub, are_nu_are_banana)
% Mişcări admise: apucă, urca, muta(Pozitia1, Pozitia2), merge(Pozitia1, Pozitia2),
% reprezentate tot prin structuri.
% Predicate:
% depl(Stare1, Miscare, Stare2)
% poate_lua(Stare)
depl(stare(la_centru, pe_cutie, la_centru, nu_are_banana),
apuca, stare(la_centru, pe_cutie, la_centru, are_banana)).
depl(stare(P, pe_sol, P, H), urca, stare(P, pe_cutie, P, H)).
depl(stare(P1, pe_sol, P1, H), muta(P1, P2), stare(P2, pe_sol, P2, H)).
depl(stare(P1, pe_sol, B, H), merge(P1, P2), stare(P2, pe_sol, B, H)).
poate_lua(stare( _ , _ , _ , are_banana)).
poate_lua(Stare1) :- depl(Stare1, Miscare, Stare2), poate_lua(Stare2).
La întrebarea
?- poate_lua(stare(la_usa, pe_sol, la_fereastra, nu_are_banana)).
yes
sistemul răspunde afirmativ: maimuţa este fericită şi mănâncă banana.
Stare1 Stare2
Mişcare
33
O analiză atentă a programului conduce la concluzia că programul dă soluţii numai
pentru anumite situaţii. În primul rând, strategia de control a mişcărilor maimuţei este
impusă de ordinea clauzelor care definesc predicatul depl. Astfel, maimuţa preferă întâi să
apuce, apoi să urce, apoi să mute cubul şi numai la urmă să mearga prin cameră. Dacă
clauza corespunzătoare mişcării merge(Pozitia1, Pozitia2) ar fi fost pusă ca prima clauză în
definiţia predicatului de deplasare, maimuţa ar fi mers la infinit prin cameră fără să mai
ajungă sa mute cubul sau să apuce banana. Chiar pentru ordinea dată a clauzelor, dacă se
pune întrebarea
?- poate_lua(stare(X, pe_sol, la_fereastra, nu_are_banana)).
deci dacă interesează din ce poziţii maimuţa poate lua banana, rezolvarea dată nu este total
satisfăcătoare deoarece programul are o infinitate de soluţii. La cererea repetată a unei
soluţii, se va afişa întotdeauna valoarea:
X = la_fereastra
Considerând din nou modelul spaţiului stărilor, se observă că în acest caz este vorba de
un spaţiu de căutare de tip graf în care o aceeaşi stare poate fi descoperită şi redescoperită la
infinit prin parcurgerea unui ciclu de tranziţii de stări în acest graf. Aşa cum este arătat în
[Flo93], astfel de cazuri trebuie tratate prin introducerea unor liste de stări parcurse (listele
FRONTIERA şi TERITORIU) care să împiedice parcurgerea repetată a unor stări deja
parcurse.
Pericolul de apariţie a unor cicluri infinite datorită parcurgerii unui spaţiu de căutare
graf nu este specific limbajului Prolog şi poate să apară în orice implementare, în aceste
cazuri. Ceea ce este neobişnuit în raport cu alte limbaje este faptul că semnificaţia
declarativă a programului este corectă, indiferent de ordonarea clauzelor, în timp ce
programul este procedural incorect, având comportări diferite în funcţie de această
ordonare. Rezolvarea problemei ar mai trebui completată cu afişarea stărilor şi a mişcărilor
executate de maimuţă pentru a ajunge în starea finală în care ea poate apuca banana.
Modalităţi de eliminare a ciclurilor infinite de tipul celor ce apar în această problemă vor fi
discutate in partea a doua a lucrării.
4.2 Controlul procesului de backtracking: cut şi fail
Sistemul Prolog intră automat într-un proces de backtraking dacă acest lucru este necesar
pentru satisfacerea unui scop. Acest comportament poate fi în unele situaţii deosebit de util,
dar poate deveni foarte ineficient în alte situaţii. Se consideră următorul exemplu de
program în care se definesc valorile unei funcţii:
f(X, 0) :- X < 3. % 1
f(X, 2) :- 3 =< X, X < 6. % 2
f(X, 4) :- 6 =< X. % 3
La întrebarea:
34
?- f(1, Y).
Y = 0
sistemul răspunde indicând că valoarea funcţiei pentru X=1 este Y=0. Dacă se pune
întrebarea formată din conjuncţia de scopuri:
?- f(1, Y), 2 < Y.
no
sistemul semnalează eşec. Arborii de deducţie corespunzători acestor răspunsuri sunt
prezentaţi în figura 9.
Figura 9. Arborii de deducţie a scopurilor f(1,Y) şi f(1,Y),2 < Y
Se observă că se încearcă resatisfacerea primului scop cu regulile 2 şi 3, deşi acest
lucru este evident inutil datorită semanticii acestor reguli. Cel care a scris aceste reguli poate
cu uşurinţă constata că, dacă o valoare mai mică decât 3 pentru X duce la eşecul scopului S
din conjuncţia de scopuri f(X, Y), S, este inutil să se încerce resatisfacerea scopului f,
deoarece aceasta nu este posibilă [Bra88]. Se poate împiedica încercarea de resatisfacere a
scopului f(X, Y) prin introducerea predicatului cut.
Predicatul cut, notat cu atomul special !, este un predicat standard, fără argumente,
care se îndeplineşte (este adevărat) întotdeauna şi nu poate fi resatisfăcut.
Predicatul cut are următoarele efecte laterale:
• La întâlnirea predicatului cut toate selecţiile făcute între scopul antet de regulă şi
cut sunt "îngheţate", deci marcajele de satisfacere a scopurilor sunt eliminate, ceea
ce duce la eliminarea oricăror altor soluţii alternative pe aceasta porţiune. O
încercare de resatisfacere a unui scop între scopul antet de regula şi scopul curent va
eşua.
• Dacă clauza în care s-a introdus predicatul cut reuşeşte, toate clauzele cu acelasi
antet cu aceasta, care urmează clauzei în care a apărut cut vor fi ignorate. Ele nu se
mai folosesc în încercarea de resatisfacere a scopului din antetul clauzei care
conţine cut.
f(1,Y)
f(1,0)
X=1
Y=0
1<3
SUCCES
f(1,Y), 2<Y
f(1,0)
X=1
Y=0
2<0
INSUCCES
1=<3
f(1,2)
X=1
Y=2
f(1,4)
X=1
Y=4
INSUCCES
3=<1 6=<1
INSUCCES
35
• Pe scurt, comportarea predicatului cut este următoarea:
(C1) H :- D1, D2, …, Dm, !, Dm+1, …, Dn.
(C2) H :- A1, …, Ap.
(C3) H.
Dacă D1, D2, …, Dm sunt satisfăcute, ele nu mai pot fi resatisfăcute datorită lui cut.
Dacă D1, …, Dm sunt satisfăcute, C2 şi C3 nu vor mai fi utilizate pentru
resatisfacerea lui H. Resatisfacerea lui H se poate face numai prin resatisfacerea
unuia din scopurile Dm+1, …, Dn, dacă acestea au mai multe soluţii.
Utilizând predicatul cut, definiţia funcţiei f(X, Y) poate fi rescrisă mult mai eficient
astfel:
f(X, 0) :- X < 3, !.
f(X, 2) :- 3 =< X, X < 6, !.
f(X, 4) :- 6 =< X.
Predicatul cut poate fi util în cazul în care se doreşte eliminarea unor paşi din deducţie
care nu conţin soluţii sau eliminarea unor căi de căutare care nu conţin soluţii. El permite
exprimarea în Prolog a unor structuri de control de tipul:
dacă condiţie atunci acţiune1
altfel acţiune2
astfel:
daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1.
daca_atunci_altfel(Cond, Act1, Act2) :- Act2.
Se observă însă că există două contexte diferite în care se poate utiliza predicatul cut:
într-un context predicatul cut se introduce numai pentru creşterea eficienţei programului,
caz în care el se numeşte cut verde; în alt context utilizarea lui cut modifică semnificaţia
procedurală a programului, caz în care el se numeşte cut roşu.
Exemplul de definire a funcţiei f(X, Y) cu ajutorul lui cut este un exemplu de cut
verde. Adăugarea lui cut nu face decât să crească eficienţa programului, dar semnificaţia
procedurală este aceeaşi, indiferent de ordinea în care se scriu cele trei clauze.
Utilizarea predicatului cut în definirea predicatului asociat structurii de control
daca_atunci_altfel introduce un cut roşu deoarece efectul programului este total diferit
dacă se schimbă ordinea clauzelor. Introducerea unui cut roşu modifică corespondenţa
dintre semnificaţia declarativă şi semnificaţia procedurală a programelor Prolog.
Se consideră exemplul de definire a predicatului de aflare a minimului dintre două
numere, în următoarele două variante:
36
min1(X, Y, X) :- X =< Y, !. % cut verde
min1(X, Y, Y) :- X > Y.
min2(X, Y, X) :- X =< Y, !. % cut roşu
min2(X, Y, Y).
În definiţia predicatului min1 se utilizează un cut verde; el este pus pentru creşterea
eficienţei programului, dar ordinea clauzelor de definire a lui min1 poate fi schimbată fără
nici un efect asupra rezultatului programului. În cazul predicatului min2 se utilizează un cut
roşu, asemănător structurii daca_atunci_altfel. Dacă se schimbă ordinea clauzelor de
definire a predicatului min2:
min2(X, Y, Y).
min2(X, Y, X) :- X =< Y, !.
rezultatul programului va fi evident incorect pentru valori X < Y.
În anumite cazuri efectul introducerii predicatului cut poate fi mai subtil. Se consideră
din nou definiţia predicatului membru:
membru(X, [X | _]).
membru(X, [ _ |Y]) :- membru(X, Y).
şi o definiţie alternativă
membru1(X, [X| _]) :- !.
membru1(X, [ _ |Y]) :- membru1(X, Y).
Introducerea predicatului cut în definiţia primei clauze a predicatului membru1 este
justificată datorită creşterii eficienţei programului. Odată ce s-a descoperit că un element
este membru al unei liste, este inutilă încercarea de resatisfacere a scopului. Cu toate acestea
predicatul cut de mai sus nu este un cut verde, deoarece el schimbă comportarea
programului în cazul în care se pun întrebări în care X este variabila neinstanţiată în
predicatele membru şi membru1.
?- membru(X, [a, b, c]).
X = a;
X = b;
X = c;
no
trei soluţii pentru membru
?- membru1(X, [a, b, c]).
X = a;
no
37
o soluţie pentru membru1.
Efectul introducerii predicatului cut asupra semnificaţiei declarative a programelor
Prolog poate fi rezumat astfel:
p :- a, b. % Semnificaţia declarativă este:
p :- c. % (a ∧ b) ∨ c
% indiferent de ordinea clauzelor (în general).
p :- a, !, b. % Semnificaţia declarativă este:
p :- c. % (a ∧ b) ∨ (~ a ∧ c).
p :- c. % Semnificaţia declarativă este:
p :- a, !, b. % c ∨ (a ∧ b).
Oportunitatea utilizării unui cut roşu sau a unui cut verde este, dintr-un anumit punct
de vedere, similară cu cea a utilizării sau nu a salturilor în limbajele de programare clasică.
Dacă s-ar rescrie predicatul daca_atunci_altfel folosind un cut verde, definiţia lui ar fi:
daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1.
daca_atunci_altfel(Cond, Act1, Act2) :- not (Cond), !, Act2.
unde predicatul not(Cond) este satisfăcut dacă scopul Cond nu este satisfăcut. O astfel de
definiţie implică evaluarea lui Cond de două ori şi, dacă Cond se defineşte ca o conjuncţie
de scopuri, posibil sofisticate, atunci ineficienţa utilizării unui cut verde în acest caz este
evidentă. De la caz la caz, programatorul în Prolog trebuie să aleagă între claritate, deci
păstrarea corespondenţei semnificaţiei declarative cu cea procedurală, şi eficienţă.
În multe situaţii interesează atât condiţiile de satisfacere a scopurilor, cât şi condiţiile
de nesatisfacere a acestora, deci de eşec. Fie următoarele fapte Prolog:
bun(gelu).
bun(vlad).
bun(mihai).
Pe baza acestor fapte se poate obţine un răspuns afirmativ despre bunătatea lui Gelu,
Vlad şi Mihai şi un răspuns negativ dacă se întreabă despre bunătatea oricărei alte persoane.
Din acest exemplu se poate vedea că limbajul Prolog foloseşte un model de raţionament
minimal numit ipoteza lumii închise. Conform acestui model, tot ceea ce nu este ştiut de
program, deci afirmat explicit ca adevărat în program, este considerat fals.
Limbajul Prolog permite exprimarea directă a eşecului unui scop cu ajutorul
predicatului fail. Predicatul fail este un predicat standard, fără argumente, care eşueaza
întotdeauna. Datorită acestui lucru introducerea unui predicat fail într-o conjuncţie de
scopuri, de obicei la sfârşit, căci după fail tot nu se mai poate satisface nici un scop,
determină intrarea în procesul de backtracking.
38
Dacă fail se întâlneste după predicatul cut, nu se mai face backtracking. Enunţul "Un
individ este rău dacă nu este bun." se poate exprima astfel:
bun(gelu).
bun(vlad).
bun(mihai).
rau(X) :- bun(X), !, fail.
rau(X).
?- rau(gelu).
no
?- rau(mihai).
no
?- rau(petru).
yes
Dacă predicatul fail este folosit pentru a determina eşecul, cum este cazul exemplului
de mai sus, este de obicei precedat de predicatul cut, deoarece procesul de backtracking pe
scopurile care îl preced este inutil, scopul eşuând oricum datorită lui fail.
Există cazuri în care predicatul fail este introdus intenţionat pentru a genera procesul
de backtracking pe scopurile care îl preced, proces interesant nu din punctul de vedere al
posibilităţii de resatisfacere a scopului ce conţine fail, ci din punctul de vedere al efectului
lateral al acestuia.
rosu(mar).
rosu(cub).
rosu(soare).
afisare(X) :- rosu(X), write(X), fail.
afisare( _ ).
Scopul afisare(X) va afişa toate obiectele roşii cunoscute de programul Prolog datorită
procesului de backtracking generat de fail; în acest fel s-a relizat prin fail o iteraţie peste
faptele rosu( ). Clauza afisare( _ ) este adăugată pentru ca răspunsul final la satisfacerea
scopului să fie afirmativ. În acest caz, introducerea unui cut înainte de fail ar fi determinat
numai afişarea primului obiect roşu din program.
Revenind la combinaţia !,fail, se consideră în continuare implementarea în Prolog a
afirmaţiei: "Mihai iubeşte toate sporturile cu excepţia boxului". Această afirmaţie poate fi
exprimată în pseudocod sub forma:
dacă X este sport şi X este box
atunci Mihai iubeşte X este fals
altfeldacă X este sport
39
atunci Mihai iubeşte X este adevărat
şi tradusă în Prolog astfel:
iubeste(mihai, X) :- sport(X), box(X), !, fail.
iubeste(mihai, X) :- sport(X).
Predicatul cut utilizat aici este un cut roşu. Combinaţia !, fail este deseori utilizată în
Prolog şi are rolul de negaţie. Se mai spune că limbajul Prolog modelează negaţia ca eşec al
satisfacerii unui scop (negaţia ca insucces), aceasta fiind de fapt o particularizare a ipotezei
lumii închise. Combinaţia !, fail este echivalentă cu un predicat standard existent în Prolog,
predicatul not. Predicatul not admite ca argument un predicat Prolog şi reuşeşte dacă
predicatul argument eşuează. Utilizând acest predicat, ultimul exemplu dat se poate exprima
în Prolog astfel:
iubeste(mihai, X) :- sport(X), not(box(X)).
iubeste(mihai, X) :- sport(X).
Un alt predicat standard este predicatul call, care admite ca argument un predicat
Prolog şi are ca efect încercarea de satisfacere a predicatului argument. Predicatul call
reuşeşte dacă predicatul argument reuşeşte şi eşuează în caz contrar. Utilizând acest
predicat, se poate explicita efectul general al predicatului standard not în următorul mod:
not(P) :- call(P), !, fail.
not(P).
Atât predicatul not cât şi predicatul call sunt predicate de ordinul II în Prolog deoarece
admit ca argumente alte predicate. Secţiunea următoare va prezenta mai multe astfel de
predicate.
4.3 Predicate predefinite ale limbajului Prolog
Predicatele prezentate până acum, cu excepţia predicatului call, sunt predicate bazate pe
logica cu predicate de ordinul I, modelul Prolog cu semnificaţia declarativă a programelor
urmărind îndeaproape modelul logic. În continuare se prezintă o serie de predicate Prolog
care nu mai pot fi asimilate cu logica cu predicate de ordinul I, respectiv: predicate ce decid
asupra caracterului unor argumente, predicate de control a fluxului de execuţie a
programului şi predicate de ordinul II, numite uneori şi metapredicate. Metapredicatele
acceptă ca argumente alte predicate Prolog, fapte sau reguli, şi sunt interesante în special
prin efectul lateral pe care îl produc. Toate tipurile de predicate menţionate reprezintă
extinderea modelului programării logice cu tehnici de programare care cresc puterea de
calcul a limbajului. Majoritatea predicatelor prezentate în continuare sunt standard şi se
găsesc predefinite în orice implementare, o parte sunt specifice mediului ARITY Prolog,
acestea fiind indicate ca atare. O implementare particulară poate conţine şi alte predicate
predefinite suplimentare celor prezentate aici.
40
(a) Predicate de clasificare a termenilor
• var(X)
Predicatul var(X) reuşeşte dacă X este o variabilă neinstanţiată şi eşuează în cazul în
care X este o variabila instanţiată sau o constantă.
Exemple:
?- var(5).
no
?- var(mihai).
no
?- var(X).
yes
• novar(X)
Predicatul novar(X) este adevărat dacă X nu este o variabilă neinstanţiată. Acest
predicat este opusul prdicatului var(X) şi s-ar putea defini astfel:
novar(X) :- var(X), !, fail.
novar( _ ).
• atom(X)
Predicatul atom(X) reuşeşte dacă X este un atom Prolog şi eşuează în caz contrar.
Exemple:
?- atom(coco).
yes
?- atom(100).
no
?- atom(carte(prolog, clocksin)).
no
• integer(X)
Predicatul integer(X) reuşeşte dacă X este un număr întreg.
Se consideră urmatorul exemplu de program Prolog care transformă o expresie
reprezentată printr-o sumă de variabile şi constante într-o expresie care conţine suma
variabilelor plus o constantă care reprezintă suma tuturor constantelor din expresie [CM84].
Predicatul simplif are două argumente: primul argument este forma iniţială a expresiei,
argument instanţiat, iar cel de al doilea reprezintă expresia simplificată, sintetizată de
program.
% simplif(Expresie_iniţială, Expresie_simplificată)
41
% Predicat ajutator simpl
% simpl(Ex_iniţială, Suma_acumulată, Ex_simplificată).
simplif(Expr, Expr1) :- simpl(Expr, 0, Expr1).
simpl(Expr + A, N, Expr1) :-
integer(A), !,
N1 is N + A,
simpl(Expr, N1, Expr1).
simpl(Expr + A, N, Expr1 + A) :-
atom(A), !,
simpl(Expr, N, Expr1).
simpl(Rest, N, N1) :-
integer(Rest), !,
N1 is Rest + N.
simpl(Rest, 0, Rest) :- !.
simpl(Rest, N, N + Rest).
La întrebarea
?- simplif(x + 40 + y + 1 + 55 + x + 2, E).
programul răspunde
E = 98 + x + y + x
În programul de mai sus definirea predicatului simplif s-a făcut pe baza unui predicat
ajutător simpl care conţine un argument suplimentar. Acest argument, instanţiat la 0 în
cazul primului apel, este folosit ca o variabilă de acumulare pentru calculul sumei
constantelor întregi din expresie.
• atomic(X)
Pe baza predicatelor atom(X) şi integer(X) se poate defini predicatul atomic(X) care
este adevărat dacă X este fie întreg, fie atom Prolog:
atomic(X) :- atom(X).
atomic(X) :- integer(X).
De multe ori predicatul atomic(X) este predefinit în sistem.
42
(b) Predicate de control
Predicatul true reuşeşte întotdeauna, iar fail eşuează întotdeauna.
Predicatul repeat simulează structurile de control repetitive din limbajele clasice de
programare. El are o infinitate de soluţii, putându-se resatisface de oricâte ori este nevoie,
fără a umple stiva. Definiţia lui ar putea fi:
repeat.
repeat :- repeat.
Acest predicat este predefinit in mediul ARITY. În ARITY Prolog există şi alte
predicate predefinite care simulează structurile de control din programarea procedurală:
ifthen(+Conditie, +Scop), pentru dacă-atunci; ifthenelse(+Conditie, +Scop-then, +Scop-
else), pentru dacă-atunci-altfel; şi case([Conditie1 -> Scop1, … Conditien -> Scopn | Scop-
default), sau case([Conditie1 -> Scop1, … Conditien -> Scopn), pentru case.
O predicat echivalent ciclului for, care nu este prefedinit în ARITY, se poate
implementa astfel:
% for(Variabla, ValoareInitiala, ValoareFinala, Pas)
for(I, I, I, 0).
for( _, _ , _ , 0) :- !, fail.
for(I, I, F, S) :-
S > 0, (I > F, !, fail ; true)
; S < 0, (I < F, !, fail ; true).
for(V, I, F, S) :- I1 is I + S, for(V, I1, F, S).
Exemple de apeluri:
a :- for(X, 10, -10, -3.5), write(X), tab(2), fail ; true.
b :- for(X, -10, 10, 3.5), write(X), tab(2), fail ; true.
c :- for(X, 10, 10, 0), write(X), tab(2), fail ; true.
?- a.
10 6.5 3.0 -0.5 -4.0 -7.5
yes
?- b.
-10 -6.5 -3.0 0.5 4.0 7.5
yes
?- c.
10
yes
Predicatul for eşuează pentru apeluri incorecte (cicluri infinite) de genul:
for(X, -10, 10, 0).
for(X, 10, -10, 2).
for(X, -10, 10, -2).
43
(c) Predicate de tratare a clauzelor drept termeni
Predicatele din această categorie permit: construirea unei structuri care reprezintă o
clauză în baza de cunoştinte, identificarea clauzelor din program, adăugarea sau eliminarea
unor clauze, reprezentate printr-o structură, în baza de cunoştinţe a sistemului.
• clause(Antet, Corp)
Satisfacerea scopului clause(Antet, Corp) necesită unificarea argumentelor Antet şi
Corp cu antetul şi corpul unei clause existente în baza de cunoştinţe Prolog. La apelul
acestui predicat, Antet trebuie să fie instanţiat astfel încât predicatul principal al clauzei să
fie cunoscut. Dacă nu există clauze pentru acest predicat, scopul eşuează. Dacă există mai
multe clauze care definesc predicatul Antet, scopul clause(Antet, Corp) va avea mai multe
soluţii, fiecare soluţie corespunzând unei clauze de definire a predicatului Antet. Dacă o
clauză este fapt, Corp se instanţiază la constanta true.
Exemplu. Considerând definiţia predicatului de concatenare a două liste ca fiind deja
introdusă în baza de cunoştinţe Prolog:
% conc(Lista1, Lista2, ListaRez)
conc([], L, L).
conc([Prim|Rest1], L2, [Prim|Rest3]) :- conc(Rest1, L2, Rest3).
se obţin următoarele rezultate:
?- clause(conc(A, B, C), Corp).
A = [ ], B = _004C, C = _004C, Corp = true;
A = [_00F0|_00F4], B = _004C, C = [_00F0|_00FC],
Corp = conc (_00F4, _004C, _00FC);
no
Utilizând predicatul clause se poate defini un predicat listc de afişare a clauzelor care
definesc un predicat:
% listc(Antet) - afişează toată clauzele din baza de cunoştinţe
% care definesc scopul Antet
listc(Antet) :-
clause(Antet, Corp),
afis(Antet, Corp),
write('.'), nl, fail.
listc( _ ).
afis(Antet, true) :- !, write(Antet). %1
afis(Antet, Corp) :- write(Antet), write(':'), write('-'), write(Corp).
Efectul acestui program, coinsiderând definiţia anterioară a scopului conc, este următorul:
44
?- listc(conc(_, _, _)).
conc([], _0034, _0034).
conc([_0108|_010C], _0034, [_0108|_0114]:-conc(_010C, _0034, _0114).
În definirea primei clauze a predicatului afis utilizarea predicatului cut este esenţială
deoarece afişarea clauzelor este bazată pe procesul de backtracking generat intenţionat de
introducerea predicatului fail în prima clauză a predicatului listc. Regula %1 din predicatul
afis este necesară deoarece faptele sunt reguli având corpul format din scopul true. Această
reprezentare ne arată că faptele şi regulile au aceeaşi reprezentare în Prolog (toate reprezintă
clauze).
Exemplu. Pentru următorul predicat:
p(1).
p(X) :- X > 2, X < 5.
p(X).
predicatul clause dă următoarele răspunsuri:
?- clause(p(X), Corp).
X = 1, Corp = true ;
X = _0038, Corp = _0038 > 2 , _0038 < 5 ;
X = _0038, Corp = true.
• asserta(Clauza), assertz(Clauza), assert(Clauza),
Predicatul asserta(Clauza) reuşeşte întotdeauna o singură dată şi are ca efect lateral
adăugarea clauzei Clauza la începutul bazei de cunoştinţe Prolog. Predicatele
assertz(Clauza) şi assert(Clauza) au o comportare similară, cu excepţia faptului că
argumentul Clauza este adăugat la sfârşitul bazei de cunoştinţe. Argumentul Clauza trebuie
să fie instanţiat la o valoare ce reprezintă o clauză Prolog. Acţiunea de adăugare a unei
clauze prin asserta, assert sau assertz nu este eliminată de procesul de backtracking.
Clauza adăugată va fi eliminată numai în urma unei cereri explicite prin intermediul
predicatului retract.
• retract(Clauza)
Predicatul retract(Clauza) are ca efect eliminarea din baza de cunoştinţe a unei clauze
care unifică cu argumentul Clauza. Predicatul reuşeşte dacă o astfel de clauza exista; la
fiecare resatisfacere se elimină din baza de cunoştinţe o nouă clauză care unifică cu
argumentul. Clauza trebuie sa fie argument instanţiat la apel. Odată ce o clauză este
eliminată din baza de cunoştinţe prin retract, clauza nu va mai fi readaugată dacă se face
backtracking pe retract.
Exemplu.
45
adaug :- asserta(prezent(nero)), asserta(prezent(cezar)).
scot :-retract(prezent(nero)).
actiune1 :- adaug, listc(prezent(X)).
actiune2 :- scot, listc(prezent(X)).
?- actiune1.
prezent(nero).
prezent(cezar).
?- actiune2.
prezent(cezar).
Utilizând predicatul retract, se poate defini un predicat care are rolul de a elimina din
baza de cunoştinţe toate clauzele care definesc un predicat. Acest predicat, numit
retractall(Antet), este predefinit în anumite implementări Prolog. Definiţia lui pe baza
predicatului retract este:
% retractall(Antet) - elimină toate clauzele care definesc scopul Antet
retractall(Antet) :- retract(Antet), fail.
retractall(Antet) :- retract( (Antet :- Corp) ), fail.
retractall( _ ).
Observaţie: În ARITY Prolog, atunci când se adaugă/elimină o regulă în/din baza de date
în mod dinamic cu assert/retract regula trebuie pusă între paranteze rotunde sau dată ca o
structură:
assert( (p(X) :- X=1 ; X=2) ).
retract( ':-'(p(X), ';'(X=1, X=2))).
Predicatele asserta, assertz şi retract pot fi utile în diverse situaţii datorită efectelor
lor laterale. Ele pot simula, de exemplu, un fel de mecanism de variabile globale în Prolog.
Se consideră exemplul de implementare a unui generator de numere aleatoare în Prolog. Se
defineşte predicatul aleator(R, N) care generează un număr aleator N în intervalul [1..R]
pornind de la o sămânţă fixată şi folosind metoda congruenţei liniare. De fiecare dată când
trebuie generat un număr aleator, valoarea acestuia este determinată folosind sămânţa
existentă şi o nouă sămânţă este calculată şi memorată ca fapt Prolog până la noul apel.
Vechea sămânţă este eliminată din baza de cunoştinţe .
% aleator(R, N) - instanţiază N la un număr aleator între 1 şi R
samanta(11).
aleator(R, N) :-
samanta(S),
modul(S, R, N1),
N is N1 + 1,
retract(samanta(S)),
46
N2 is (125 * S +1),
modul(N2, 4096, SamantaNoua),
asserta(samanta(SamantaNoua)).
modul(X, Y, X) :- X < Y.
modul(X,Y,Z) :- % Predicatul modulo este predefinit
X >= Y, % în anumite implementări
X1 is X - Y,
modul(X1, Y, Z).
La apelul predicatului se obţin următoarele rezultate:
?- aleator(100, N).
N = 14
?- aleator(100, N).
N = 27
?- aleator(100, N).
N = 48
Dacă se doreşte afişarea continuă a numerelor aleatoare, o primă variantă este aceea de
a crea un ciclu infinit de afişare a acestor numere, efect realizat de predicatul nr_aleat(R)
care afişează numere aleatoare în intervalul [1..R].
% nr_aleat(R) - afişează la infinit numere aleatoare între 1 şi R
nr_aleat(R) :- repeat, aleator(R, X), write(X), nl, fail.
Predicatul fail introdus în definiţia predicatului nr_aleat(R) determină intrarea în
procesul de backtracking şi datorită predicatului repeat, cele două scopuri aleator(R, X) şi
write(X) se vor satisface la infinit. Trebuie observat faptul că numai scopul repeat se
resatisface; scopurile aleator şi write sunt la prima satisfacere de fiecare dată.
În cazul în care se doreşte construirea unui ciclu care se va termina dacă o anumită
condiţie este îndeplinită, se poate reformula predicatul de generare a numerelor aleatoare
astfel:
nr_aleat1(R) :-
repeat, aleator(R, X), write(X), nl,
write('Continuati? [d/n]'), read('n').
Apelând predicatul se va obţine următoarea secvenţă de numere aleatoare:
?- nr_aleat1(10).
4
Continuati? d.
7
Continuati? d.
47
8
Continuati? n.
yes
• functor(Term, Funct, N)
Predicatul reuşeşte dacă cele trei argumente unifică astfel: Term este o structură cu
functor Funct şi aritate N. Predicatul poate fi folosit în două moduri:
(1) Term este argument instanţiat şi predicatul reuşeşte dacă Term este atom sau
structură şi eşuează în caz contrar. În caz de succes, Funct şi N sunt instanţiate
la functorul, respectiv aritatea lui Term. Un atom este considerat ca fiind o
structură de aritate 0.
(2) Term este argument neinstanţiat iar Funct şi N sunt instanţiate, specificând
functorul şi numărul de argumente. În acest caz scopul reuşeşte întotdeauna şi
Term va fi instanţiat la o structură cu functorul şi numărul de argumente
specificate. Argumentele structurii construite în acest mod sunt neinstanţiate.
Exemple:
?- functor(auto(honda, culoare(roşie)), Funct, N).
Funct = auto, N = 2
?- functor(a + b, F, N).
F = +, N = 2
?- functor(albastru, F, N).
F = albastru, N = 0
?- functor [a, b, c], !, 3).
no
?- functor(Term, pasi_plan, 3).
Term = pasi_plan(_0, _0, _0)
• arg (N, Term, Arg)
Predicatul arg (N, Term, Arg) are ca efect obţinerea celui de al N-lea argument al
unei structuri Term, acest argument devenind instanţierea lui Arg. N şi Term trebuie să fie
argumente instanţiate. Arg poate fi instanţiat, caz în care predicatul reuşeşte dacă Arg este
argumentul N din structura Term, sau Arg poate fi neinstanţiat, caz în care se va instanţia la
cel de al N-lea argument al lui Term.
?- arg(2, poseda(mihai, frumoasa(portocala)), Arg).
Arg = frumoasa(portocala).
?- arg (2,[a, b, c], X).
X = [b, c]
?- arg(1, a + (b + c), b).
48
no
• Scop = .. [Functor | ListArg]
Predicatul =.., numit univ, este un predicat folosit ca operator infixat. El poate fi
folosit în trei moduri:
(1) Scop este instanţiat. În acest caz predicatul reuşeşte, dacă Scop este o
structură, iar Functor şi ListArg se instanţiază la functorul, respectiv lista
argumentelor acelei structuri.
(2) Scop este neinstanţiat, iar Functor şi ListArg sunt instanţiate la un functor,
respectiv la o listă de argumente a unei structuri. Dacă valorile lui Functor şi
ListArg sunt corecte, predicatul reuşeşte şi Scop va fi instanţiat la structura
creată cu functorul şi lista de argumente date.
(3) Toţi trei parametrii sunt instanţiati şi predicatul reuşeşte dacă Functor şi
ListArg sunt functorul, respectiv lista argumentelor structurii Scop.
Acest predicat are o importanţă deosebită în Prolog deoarece el permite sinteza
dinamică a scopurilor Prolog, deci construirea de clauze pe parcursul execuţiei
programului. Lucrul acesta este posibil deoarece clauzele Prolog au tot forma unor structuri
formate din functor şi argument, sintaxa codului fiind aceeaşi cu sintaxa datelor în Prolog.
Exemple:
?- sir(a, b, c) =.. X.
X = [sir, a, b, c]
?- (f(a) + g(b)) =.. [+, f(X), Y].
X = a, Y = g(b)
?- a + b + c =.. L. % a + b + c = '+'(a, '+'(b, c))
L=[+, a+b, c]
?- a * b + c =.. L. % a * b + c = '+'('*'(a, b), c)
L = [+, a * b, c]
?- a + b * c =.. L. % a + b * c = '+'(a, '*'(b, c))
L = [+, a, b * c]
?- Scop =.. [parinte, mihai, gina].
Scop = parinte(mihai, gina)
?- Scop =.. [membru, a, [a, b, c]].
Scop = membru(a, [a, b, c])
?- conc([1, 2], [3], [1, 2, 3]) =.. [Functor | ListArg]
49
Functor = conc, ListArg = [[1, 2], [3], [1, 2, 3]]
?- S =.. [f, X, a,Y].
S = f( _004C, a, _0060 ), X = _004C, Y = _0060
?- f =.. L.
L = [f]
Folosind univ putem verifica faptul că listele se reprezintă prin structuri cu punct de
forma: '.'(PrimulElement, RestulListei). Lista [1, 2] este totuna cu '.'(1, [2]) sau cu
'.'(1, '.'(2, []).
Exemplu.
?- [1,2] =.. L.
L = [. , 1, [2]].
• listing, listing(+Nume), listing(+Nume/Aritate), listing(+[Nume/Aritate…])
Predicatul listing tipăreşte din baza de cunoştinţe toate clauzele, toate clauzele unui
predicat sau toate clauzele unor predicate dintr-o listă. De exemplu, pentru predicatele:
p.
p(1).
p(X) :- X > 2, X < 5.
p(X).
p(1,2).
p(X,Y) :- X < Y.
se pot face următoarele interogări (redenumirile variabilelor sunt făcute automat de sistem):
?- listing(p/1).
p(1).
p(A) :- A > 2, A < 5.
p(A).
yes
?- listing([p/0, p/2]).
p.
p(1,2).
p(A, B) :- A < B.
yes
(d) Predicate pentru execuţia dinamică a scopurilor
50
• call(Scop)
Se presupune că argumentul Scop este instanţiat la un scop Prolog. Predicatul
call(Scop) reuşeşte dacă Scop reuşeşte şi eşuează în caz contrar. La prima vedere acest
predicat pare redundant deoarece execuţia scopului Scop se poate face direct în Prolog.
Dacă se doreste execuţia dinamică a scopurilor care au fost sintetizate pe parcursul execuţiei
unui program Prolog, atunci predicatul call este necesar. În plus, dacă nu se cunoaşte scopul
de executat, de exemplu se doreşte execuţia a diverse scopuri cu argumente diferite sau cu
aceleaşi argumente, se poate folosi predicatul call cu argument o variabilă care se va
instanţia în funcţie de întrebarea utilizatorului sau de context.
Execuţia dinamică a scopurilor poate fi exprimată prin următoarea secvenţă de
scopuri:
... obţine(Functor), calculează(ArgList), Scop =.. [Functor | ArgList], call(Scop), ...
Se prezintă în continuare diverse variante de predicate Prolog care aplică un predicat
primit ca argument fiecărui element al unei liste sau al mai multor liste, rezultatele fiecărei
aplicări fiind cumulate într-o listă rezultat. Acest tip de predicate reprezintă o categorie de
prelucrări ale listelor, frecvent întâlnite şi necesare.
Se defineşte întâi predicatul mapcar(Pred, ListaArg, ListaRez) care primeşte două
argumente instanţiate Pred şi ListaArg şi calculează ListaRez. Pred este un predicat
Prolog care are la rândul lui două argumente: primul, instanţiat, este folosit în prelucrare
pentru obţinerea celui de al doilea şi este obţinut ca element curent din ListaArg; cel de al
doilea, neinstanţiat, este calculat de Pred şi este depus ca element curent în ListaRez. În
programul care urmează se vor folosi ca predicate Pred următoarele:
• prim(Lista, El) care obţine primul element dintr-o listă,
• neg(Element, -Element) care schimbă semnul unui element şi
• adaug(Nume, NumeFrumos) care adaugă în faţa unui nume apelativul de politeţe
dna dacă persoana este femeie şi dl dacă persoana este bărbat.
Se observă că argumentele lui Pred din definiţia lui mapcar pot fi atât elemente atomice
(atomi sau numere) cât şi liste.
% Se definesc predicatele de prelucrare a listelor: mapcar, mapcar2 şi mapcarnl.
prim([], []).
prim([X | _ ], X).
neg(X, -X).
adaug(Nume, NumeFrumos) :-
write(Nume),
write(' este femeie sau barbat (f/b)? '),
read(Sex),
(Sex = b, NumeFrumos = [dl, Nume] ;
51
Sex = f, NumeFrumos = [dna, Nume]).
% mapcar(Pred, ListaArg, ListaRez) - evaluează predicatul
% Pred pentru fiecare element din lista ListaArg şi
% construieşte rezultatul în ListaRez.
% Predicatul Pred are două argumente, atomice sau liste.
mapcar( _ , [], []).
mapcar(P, [Arg | RestArg], [X | Rest]) :-
Scop =.. [P, Arg, X],
call(Scop),
mapcar(P, RestArg, Rest).
În definirea scopului adaug s-a utilizat simbolul ;. Acesta este un operator predefinit
în Prolog care marchează o disjuncţie de scopuri, deci are semnificaţia de disjuncţie logică.
Efectul operatorului ; poate fi reprezentat în următorul fel:
X ; X :- X.
X ; Y :- Y.
Deci aşa cum operatorul Prolog , corespunde unei conjuncţii de scopuri, operatorul ;
corespunde unei disjuncţii de scopuri.
Comportarea acestui program este următoarea:
?- mapcar(prim, [[alain, turing], [ada, byron]], Rez).
Rez = [alain, ada]
?- mapcar(neg, [1, 2, 3, 4], Rez).
Rez = [-1, -2, -3, -4]
?- mapcar(adaug, [maria, mihai, george, ana], Rez).
maria este femeie sau barbat (f/b)? f
mihai este femeie sau barbat (f/b)? b
george este femeie sau barbat (f/b)? b
ana este femeie sau barbat (f/b)? f
Rez = [[dna, maria], [dl, mihai], [dl, george], [dna, ana]]
Dacă se doreste execuţia unor prelucrări similare, dar pentru predicate Prolog care
admit trei argumente, primele doua instanţiate şi cel de al treilea sintetizat de predicatul
Pred pe baza primelor două, se poate defini de o maniera similară predicatul:
mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez)
Se va demonstra funcţionarea acestui predicat în cazurile în care predicatul Pred, care
se mapează pe elementele listelor ListaArgPrim şi ListaArgSecund, este întâi
plus(A, B, Rez) care calculează în Rez suma lui A şi B, apoi conc(List1, Lista2, ListaRez)
52
care concatenează două liste şi depune rezultatul în ListaRez. Argumentele predicatului
Pred pot fi argumente atomice sau liste.
conc([], L, L).
conc([X|Rest], L, [X|Rest1]) :- conc(Rest, L, Rest1).
plus(A, B, Rez) :- Rez is A + B.
% mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez) -
% evaluează predicatul Pred pentru fiecare pereche
% de argumente, unul din ListaArgPrim, celălalt din ListaArgSecund
% şi construieşte rezultatul în ListaRez.
% Predicatul Pred are trei argumente, atomice sau liste.
mapcar2( _ , [], _ , []).
mapcar2(P, [Arg1 | RestArg1], [Arg2 | RestArg2], [X | Rest]) :-
Scop =.. [P, Arg1, Arg2, X],
call(Scop),
mapcar2(P, RestArg1, RestArg2, Rest).
?- mapcar2(plus, [1, 2, 3, 4], [10, 20, 30, 40], Rez).
Rez = [11, 22, 33, 44]
?- mapcar2(conc, [[1, 2], [a, b]], [[3, 4], [c, d]], Rez).
Rez = [[1, 2, 3, 4], [a, b, c, d]]
Se observă că este necesară definirea unui predicat de tip mapcar diferit pentru fiecare
categorie de aritate N a predicatelor care pot instanţia legal Pred. Dacă se impune restricţia
conform căreia predicatul Pred poate avea numai argumente atomice, atunci se poate defini
predicatul mapcarnl(Pred, ListaListeArg, ListaRez) cu un efect similar. ListaListeArg
este fie o listă de elemente atomice, dacă Pred are două argumente, dintre care primul
instanţiat va fi obţinut ca elementul curent al acestei liste, fie o listă de liste, unde fiecare
element listă din ListaListeArg conţine primele N-1 argumente atomice ale lui Pred.
% mapcarnl(Pred, ListaListeArg, ListaRez) -
% evaluează predicatul Pred cu primele N-1 argumente din lista
% ListaListeArg şi construieşte rezultatul în ListaRez.
% Predicatul Pred poate avea oricâte argumente, dar toate trebuie sa fie atomice.
mapcarnl(_, [], []).
Mapcarnl(P, [Arg|RestArg], [X|Rest]) :-
(atomic(Arg), FormaScop = [P,Arg,X] ;
not atomic(Arg), conc([P], Arg, Temp), conc(Temp, [X], FormaScop)),
Scop =.. FormaScop,
call(Scop),
mapcarnl(P, RestArg, Rest).
?- mapcarnl(neg, [1, 2, 3, 4], Rez]
Rez = [-1, -2, -3, -4]
53
?- mapcarnl[[1, 10], [2, 20], [3, 30], [4, 40]], Rez).
Rez = [11, 22, 33, 44].
• findall(X, Scop, Lista)
Predicatul findall(X, Scop, Lista) are ca efect obţinerea tuturor termenilor X care
satisfac predicatul Scop în baza de cunoştinţe a programului şi cumularea acestor termeni
în lista Lista. Scop trebuie să fie instanţiat la un predicat Prolog în care, în mod normal,
trebuie să apară X. Dacă Scop nu reuşeşte, findall reuşeşte, instanţiind Lista la lista vidă
(această ultimă caracteristică este specifică mediului ARITY).
Exemple:
prieten(vlad, ana).
prieten(vlad, george).
prieten(vlad, mihai).
prieten(liviu, ana).
?- findall(X, prieten(vlad, X), L).
X = _0038, L = [ana, george, mihai]
Efectul predicatului findall poate fi explicat şi prin definiţia explicită a unui predicat
cu acelaşi efect: find_all(X, Scop, Lista)
% find_all(Arg, Scop, Lista) -
% are acelaşi efect ca predicatul standard findall(Arg, Scop, Lista).
prieten(vlad, ana).
prieten(vlad, george).
prieten(vlad, mihai).
prieten(liviu, ana).
find_all(X, S, _) :- asserta(stiva(baza_stivei)),
call(S), % Scopul S conţine X.
asserta(stiva(X)), fail.
find_all( _ , _ , L) :- colecteaza([], M), !, L = M.
colecteaza(S, L) :- urmator(X), !, colecteaza([X | S], L).
colectează(L, L).
următor(X) :- retract(stiva(X)), !, X = baza_stivei.
?- find_all(X, prieten(vlad, X), L).
X = _0038, L = [ana, george, mihai].
?- find_all(X, prieten(toma, X), L).
X = _0038, L = []
54
În definiţia predicatului find_all se observă utilizarea unei tehnici de programare
Prolog interesante. Utilizând predicatul asserta se simulează o structură de stivă în Prolog.
Baza stivei este marcata de faptul stiva(baza_stivei) şi în stivă se introduc, ca fapte
suplimentare adăugate la începutul bazei de cunoştinţe Prolog, valorile X care satisfac
scopul S sub forma stiva(X), cu X instanţiat de apelul call(S). Acest lucru este executat
pentru toate soluţiile posibile ale predicatului call(S) datorită predicatului fail introdus în
finalul definiţiei primei clauze a predicatului find_all. În acest fel toate valorile lui X din
baza de cunoştinţe Prolog care satisfac S sunt introduse în stiva simulată. Cea de a doua
clauză a predicatului find_all, care se execută după ce nu mai există nici o posibilitate de
resatisfacere a primeia, are ca scop golirea stivei şi colectarea valorilor introduse în stivă,
până la baza ei, stiva(baza_stivei), în lista M.
• bagof(X, Scop, Lista)
Predicatul bagof(X, Scop, Lista) are ca efect colectarea în Lista a tuturor termenilor
X care satisfac Scop, ţinând cont de diversele instanţieri posibil diferite ale celorlalte
variabile din Scop. Scop este un argument care trebuie să fie instanţiat la un scop Prolog.
Dacă Scop nu conţine alte variabile în afara lui X, atunci efectul predicatului bagof este
identic cu cel al lui findall. Dacă Scop nu reuşeşte cel puţin o dată atunci bagof eşuează.
Exemple:
clasificare(a, vocala).
clasificare(b, consoana).
clasificare(c, consoana).
clasificare(d, consoana).
clasificare(e, vocala).
clasificare(f, consoana).
?- findall(X, clasificare(X, C), L).
X = _0038, C = _004C, L = [a, b, c, e, f] % o soluţie
?- bagof(X, clasificare(X, C), L).
X = _0038, C = consoana, L = [b, c, d, f];
X = _0038, C = vocala, L = [a, e]
no % două soluţii
• setof(X, Scop, Lista)
Predicatul setof(X, Scop, Lista) are acelaşi efect cu predicatul bagof, cu excepţia
faptului că valorile cumulate în lista Lista sunt sortate crescător şi se elimină apariţiile
multiple de valori, deci Lista devine o mulţime ordonată.Dacă Scop nu reuşeşte cel puţin o
dată atunci setof eşuează.
Considerând faptele de definire a consoanelor şi variabilelor prezentate anterior, se
poate construi următorul exemplu, în care mai introducem două fapte:
55
asserta(clasificare(d, consoana)).
asserta(clasificare(e, vocala)).
?- bagof(X, clasificare(X, C), L).
X = _0038, C = consoana, L = [d, b, c, d, f]; % consoana d apare de două ori
X = _0038, C = vocala, L = [e, a, e]; % vocala e apare de două ori
no
?- setof(X, clasificare(X, C), L).
X = _0038, C = consoana, L = [b, c, d, f]; % consoanele apar o singură dată
X = _0038, C = vocala, L = [a, e]; % vocalele apar o singură dată
no
4.4 Direcţia de construire a soluţiilor
Majoritatea programelor Prolog se bazează pe recursivitate. Ca în orice limbaj de
programare recursiv, parametrii de ieşire ai subprogramelor, deci argumentele sintetizate ale
predicatelor în cazul limbajului Prolog, pot fi calculate fie pe ramura de avans în
recursivitate, fie pe ramura de revenire din recursivitate. Se consideră două variante de
definire a predicatului de numărare a elementelor dintr-o listă. Prima varianta,
nr_elem(Lista, NrElem), construieşte soluţia (calculează numărul de elemente din listă) pe
ramura de revenire din recursivitate.
% nr_elem(Lista, NrElem)
nr_elem([], 0).
nr_elem([ _ | Rest], N) :- nr_elem(Rest, N1), N is N1 + 1.
A doua variantă, nr_elem2(Lista, NrElem) calculează numărul de elemente din listă
pe ramura de avans în recursivitate. Pentru a realiza acest lucru, se foloseşte un predicat
ajutător nr_elem1(Lista, NrAcumulat, NrElem) care are un argument suplimentar faţă de
predicatul nr_elem2. Acest argument, NrAcumulat, are rolul de variabilă de acumulare a
numărului de elemente din listă pe măsura avansului în recursivitate şi este instanţiat la
primul apel la valoarea 0. În momentul atingerii punctului de oprire din recursivitate, deci în
cazul în care lista ajunge vidă, valoarea acumulata în argumentul NrAcumulat este copiată
în cel de-al treilea parametru al predicatului nr_elem_1. Se face apoi revenirea din apelurile
recursive succesive fără a efectua nici o altă prelucrare, predicatul nr_elem_1 reuşeşte şi
trimite valoarea calculată în NrElem predicatului iniţial nr_elem2.
% nr_elem2(Lista, NrElem)
% nr_elem1(Lista, NrAcumulat, NrElem)
nr_elem2(Lista, N) :- nr_elem1(Lista, 0, N).
nr_elem1([], N, N).
nr_elem1([ _ | Rest], N1, N2) :- N is N1 + 1, nr_elem1(Rest, N, N2).
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala
Programare in prolog_pentru_inteligenta_artificiala

More Related Content

Viewers also liked

Arduino wifi shield-drivermotoarel298
Arduino wifi shield-drivermotoarel298Arduino wifi shield-drivermotoarel298
Arduino wifi shield-drivermotoarel298
Iulius Bors
 
Motoare pascupasarduinoeasydriver
Motoare pascupasarduinoeasydriverMotoare pascupasarduinoeasydriver
Motoare pascupasarduinoeasydriver
Iulius Bors
 
Curs gratuitarduino lectia4-modalitati decomunicare
Curs gratuitarduino lectia4-modalitati decomunicareCurs gratuitarduino lectia4-modalitati decomunicare
Curs gratuitarduino lectia4-modalitati decomunicare
Iulius Bors
 
Arduino toraspberrypi firmata
Arduino toraspberrypi firmataArduino toraspberrypi firmata
Arduino toraspberrypi firmata
Iulius Bors
 
Raspberry pi lcd-shield20x4
Raspberry pi lcd-shield20x4Raspberry pi lcd-shield20x4
Raspberry pi lcd-shield20x4
Iulius Bors
 
Arduino uno rfidid-12
Arduino uno rfidid-12Arduino uno rfidid-12
Arduino uno rfidid-12
Iulius Bors
 
Arduino multiplexarea le-durilor
Arduino multiplexarea le-durilorArduino multiplexarea le-durilor
Arduino multiplexarea le-durilor
Iulius Bors
 
Arduino indicatoare cp-usiram
Arduino indicatoare cp-usiramArduino indicatoare cp-usiram
Arduino indicatoare cp-usiram
Iulius Bors
 
Arduino matriceled8x8
Arduino matriceled8x8Arduino matriceled8x8
Arduino matriceled8x8
Iulius Bors
 
Asamblare statie meteo
Asamblare statie meteoAsamblare statie meteo
Asamblare statie meteo
Iulius Bors
 
Curs gratuitarduino lectia15-seismografcuexcel
Curs gratuitarduino lectia15-seismografcuexcelCurs gratuitarduino lectia15-seismografcuexcel
Curs gratuitarduino lectia15-seismografcuexcel
Iulius Bors
 
Raspberry piuln2803
Raspberry piuln2803Raspberry piuln2803
Raspberry piuln2803
Iulius Bors
 
Arduino yun arduino-wifi-ethernet-linux
Arduino yun arduino-wifi-ethernet-linuxArduino yun arduino-wifi-ethernet-linux
Arduino yun arduino-wifi-ethernet-linux
Iulius Bors
 
Curs gratuitarduino lectia10-lcd-uri
Curs gratuitarduino lectia10-lcd-uriCurs gratuitarduino lectia10-lcd-uri
Curs gratuitarduino lectia10-lcd-uri
Iulius Bors
 
Arduino flexsensorbrickbionicfinger
Arduino flexsensorbrickbionicfingerArduino flexsensorbrickbionicfinger
Arduino flexsensorbrickbionicfinger
Iulius Bors
 
Raspberry pi bmp085blueedition
Raspberry pi bmp085blueeditionRaspberry pi bmp085blueedition
Raspberry pi bmp085blueedition
Iulius Bors
 
Curs gratuitarduino lectia1-arduinoblink
Curs gratuitarduino lectia1-arduinoblinkCurs gratuitarduino lectia1-arduinoblink
Curs gratuitarduino lectia1-arduinoblink
Iulius Bors
 

Viewers also liked (18)

Arduino wifi shield-drivermotoarel298
Arduino wifi shield-drivermotoarel298Arduino wifi shield-drivermotoarel298
Arduino wifi shield-drivermotoarel298
 
Motoare pascupasarduinoeasydriver
Motoare pascupasarduinoeasydriverMotoare pascupasarduinoeasydriver
Motoare pascupasarduinoeasydriver
 
Curs gratuitarduino lectia4-modalitati decomunicare
Curs gratuitarduino lectia4-modalitati decomunicareCurs gratuitarduino lectia4-modalitati decomunicare
Curs gratuitarduino lectia4-modalitati decomunicare
 
Arduino toraspberrypi firmata
Arduino toraspberrypi firmataArduino toraspberrypi firmata
Arduino toraspberrypi firmata
 
Raspberry pi lcd-shield20x4
Raspberry pi lcd-shield20x4Raspberry pi lcd-shield20x4
Raspberry pi lcd-shield20x4
 
Arduino uno rfidid-12
Arduino uno rfidid-12Arduino uno rfidid-12
Arduino uno rfidid-12
 
Arduino multiplexarea le-durilor
Arduino multiplexarea le-durilorArduino multiplexarea le-durilor
Arduino multiplexarea le-durilor
 
Arduino indicatoare cp-usiram
Arduino indicatoare cp-usiramArduino indicatoare cp-usiram
Arduino indicatoare cp-usiram
 
Arduino sht11
Arduino sht11Arduino sht11
Arduino sht11
 
Arduino matriceled8x8
Arduino matriceled8x8Arduino matriceled8x8
Arduino matriceled8x8
 
Asamblare statie meteo
Asamblare statie meteoAsamblare statie meteo
Asamblare statie meteo
 
Curs gratuitarduino lectia15-seismografcuexcel
Curs gratuitarduino lectia15-seismografcuexcelCurs gratuitarduino lectia15-seismografcuexcel
Curs gratuitarduino lectia15-seismografcuexcel
 
Raspberry piuln2803
Raspberry piuln2803Raspberry piuln2803
Raspberry piuln2803
 
Arduino yun arduino-wifi-ethernet-linux
Arduino yun arduino-wifi-ethernet-linuxArduino yun arduino-wifi-ethernet-linux
Arduino yun arduino-wifi-ethernet-linux
 
Curs gratuitarduino lectia10-lcd-uri
Curs gratuitarduino lectia10-lcd-uriCurs gratuitarduino lectia10-lcd-uri
Curs gratuitarduino lectia10-lcd-uri
 
Arduino flexsensorbrickbionicfinger
Arduino flexsensorbrickbionicfingerArduino flexsensorbrickbionicfinger
Arduino flexsensorbrickbionicfinger
 
Raspberry pi bmp085blueedition
Raspberry pi bmp085blueeditionRaspberry pi bmp085blueedition
Raspberry pi bmp085blueedition
 
Curs gratuitarduino lectia1-arduinoblink
Curs gratuitarduino lectia1-arduinoblinkCurs gratuitarduino lectia1-arduinoblink
Curs gratuitarduino lectia1-arduinoblink
 

More from Iulius Bors

Set prizecomandatedinarduino
Set prizecomandatedinarduinoSet prizecomandatedinarduino
Set prizecomandatedinarduino
Iulius Bors
 
Raspberry pi wirelesshotspot
Raspberry pi wirelesshotspotRaspberry pi wirelesshotspot
Raspberry pi wirelesshotspot
Iulius Bors
 
Raspberry pi timelapse
Raspberry pi timelapseRaspberry pi timelapse
Raspberry pi timelapse
Iulius Bors
 
Curs gratuitarduino lectia11-muzicacuarduino
Curs gratuitarduino lectia11-muzicacuarduinoCurs gratuitarduino lectia11-muzicacuarduino
Curs gratuitarduino lectia11-muzicacuarduino
Iulius Bors
 
Curs gratuitarduino lectia8-comunicareethernet
Curs gratuitarduino lectia8-comunicareethernetCurs gratuitarduino lectia8-comunicareethernet
Curs gratuitarduino lectia8-comunicareethernet
Iulius Bors
 
Curs gratuitarduino lectia7-senzoridistanta
Curs gratuitarduino lectia7-senzoridistantaCurs gratuitarduino lectia7-senzoridistanta
Curs gratuitarduino lectia7-senzoridistanta
Iulius Bors
 
Curs gratuitarduino lectia6-senzoriforta
Curs gratuitarduino lectia6-senzorifortaCurs gratuitarduino lectia6-senzoriforta
Curs gratuitarduino lectia6-senzoriforta
Iulius Bors
 
Curs gratuitarduino lectia5-accelerometre
Curs gratuitarduino lectia5-accelerometreCurs gratuitarduino lectia5-accelerometre
Curs gratuitarduino lectia5-accelerometre
Iulius Bors
 
Curs gratuitarduino lectia3-proiectesimple-1
Curs gratuitarduino lectia3-proiectesimple-1Curs gratuitarduino lectia3-proiectesimple-1
Curs gratuitarduino lectia3-proiectesimple-1
Iulius Bors
 
Cum instaleziapachesimysql peraspberrypi
Cum instaleziapachesimysql peraspberrypiCum instaleziapachesimysql peraspberrypi
Cum instaleziapachesimysql peraspberrypi
Iulius Bors
 
Conector bluetoothmatesilver arduino-pc
Conector bluetoothmatesilver arduino-pcConector bluetoothmatesilver arduino-pc
Conector bluetoothmatesilver arduino-pc
Iulius Bors
 
Arduino yala electronicabazatapecodsecret
Arduino yala electronicabazatapecodsecretArduino yala electronicabazatapecodsecret
Arduino yala electronicabazatapecodsecret
Iulius Bors
 

More from Iulius Bors (12)

Set prizecomandatedinarduino
Set prizecomandatedinarduinoSet prizecomandatedinarduino
Set prizecomandatedinarduino
 
Raspberry pi wirelesshotspot
Raspberry pi wirelesshotspotRaspberry pi wirelesshotspot
Raspberry pi wirelesshotspot
 
Raspberry pi timelapse
Raspberry pi timelapseRaspberry pi timelapse
Raspberry pi timelapse
 
Curs gratuitarduino lectia11-muzicacuarduino
Curs gratuitarduino lectia11-muzicacuarduinoCurs gratuitarduino lectia11-muzicacuarduino
Curs gratuitarduino lectia11-muzicacuarduino
 
Curs gratuitarduino lectia8-comunicareethernet
Curs gratuitarduino lectia8-comunicareethernetCurs gratuitarduino lectia8-comunicareethernet
Curs gratuitarduino lectia8-comunicareethernet
 
Curs gratuitarduino lectia7-senzoridistanta
Curs gratuitarduino lectia7-senzoridistantaCurs gratuitarduino lectia7-senzoridistanta
Curs gratuitarduino lectia7-senzoridistanta
 
Curs gratuitarduino lectia6-senzoriforta
Curs gratuitarduino lectia6-senzorifortaCurs gratuitarduino lectia6-senzoriforta
Curs gratuitarduino lectia6-senzoriforta
 
Curs gratuitarduino lectia5-accelerometre
Curs gratuitarduino lectia5-accelerometreCurs gratuitarduino lectia5-accelerometre
Curs gratuitarduino lectia5-accelerometre
 
Curs gratuitarduino lectia3-proiectesimple-1
Curs gratuitarduino lectia3-proiectesimple-1Curs gratuitarduino lectia3-proiectesimple-1
Curs gratuitarduino lectia3-proiectesimple-1
 
Cum instaleziapachesimysql peraspberrypi
Cum instaleziapachesimysql peraspberrypiCum instaleziapachesimysql peraspberrypi
Cum instaleziapachesimysql peraspberrypi
 
Conector bluetoothmatesilver arduino-pc
Conector bluetoothmatesilver arduino-pcConector bluetoothmatesilver arduino-pc
Conector bluetoothmatesilver arduino-pc
 
Arduino yala electronicabazatapecodsecret
Arduino yala electronicabazatapecodsecretArduino yala electronicabazatapecodsecret
Arduino yala electronicabazatapecodsecret
 

Programare in prolog_pentru_inteligenta_artificiala

  • 1. A. M. Florea B. Dorohonceanu C. Frâncu Programare în Prolog pentru Inteligenţă Artificială UNIVERSITATEA “POLITEHNICA” BUCUREŞTI 1997
  • 2. 2 Partea I Limbajul Prolog Începutul programării logice poate fi atribuit lui R. Kowalski şi A. Colmerauer şi se situează la începutul anilor '70. Kowalski a plecat de la o formulă logică de tipul: S1 ∧ S2 ∧ ... Sn → S care are, în logica cu predicate de ordinul întâi semnificaţia declarativă conform căreia S1 ∧ S2 ∧ ... Sn implică S, adică dacă S1 şi S2 ... şi Sn sunt fiecare adevărate atunci şi S este adevărat, şi a propus o interpretare procedurală asociată. Conform acestei interpretări, formula de mai sus poate fi scrisă sub forma: S dacă S1 şi S2 ... şi Sn şi poate fi executată ca o procedură a unui limbaj de programare recursiv, unde S este antetul procedurii şi S1, S2, ... Sn corpul acesteia. Deci, pe lângă interpretarea declarativă, logică, a unei astfel de formule, formula poate fi interpretată procedural astfel: pentru a executa S se execută S1 şi S2 ... şi Sn. În aceeaşi perioadă, A. Colmerauer şi colectivul lui de cercetare de la Universitatea din Marsilia au dezvoltat un limbaj de implementare a acestei abordări, pe care l-au denumit Prolog, abreviere de la "Programmation et Logique". De atunci şi până în prezent, limbajul Prolog s-a impus ca cel mai important limbaj de programare logică şi s-au dezvoltat numeroase implementări, atât ale unor interpretoare cât şi ale unor compilatoare ale limbajului. Limbajul Prolog este un limbaj declarativ susţinut de o componentă procedurală. Spre deosebire de limbajele procedurale, cum ar fi C sau Pascal, în care rezolvarea problemei este specificată printr-o serie de paşi de execuţie sau acţiuni, într-un limbaj declarativ problema este specificată prin descrierea universului problemei şi a relaţiilor sau funcţiilor existente între obiecte din acest univers. Exemple de astfel de limbaje sunt cele funcţionale, de exemplu Lisp, Scheme, ML, şi cele logice, de exemplu Prolog. Deşi iniţial a fost gândit pentru un set restrâns de probleme, Prolog a devenit cu timpul un limbaj de uz general, fiind o unealtă importantă în aplicaţiile de inteligenţă artificială [CM84, Bra88, SS86]. Pentru multe probleme, un program Prolog are cam de 10 ori mai puţine linii decât echivalentul lui în Pascal. În 1983, cercetătorii din Japonia au publicat un plan ambiţios de creare a unor calculatoare de generaţia a 5-a pentru care Prolog era limbajul de asamblare. Planul nu a
  • 3. 3 reuşit, dar acest proiect a marcat o dezvoltare deosebită a interpretoarelor şi compilatoarelor de Prolog, precum şi o creştere mare a numărului de programatori în acest limbaj. Multe clase de probleme poate fi rezolvate în Prolog, existând anumite categorii care sunt rezolvabile mult mai uşor în Prolog decât în orice alt limbaj procedural. Astfel de probleme sunt în principal cele dedicate prelucrării simbolice sau care necesită un proces de căutare a soluţiei într-un spaţiu posibil de transformări ale problemei. Prezentarea limbajului Prolog ce urmează este în principal orientată pe descrierea limbajului Prolog standard. Exemplele de programe din această parte cât şi din partea a doua sunt rulate utilizând interpretorul ARITY Prolog pe microcalculatoare de tip IBM/PC, mediul de programare ARITY Prolog şi particularităţile lui sintactice fiind prezentate în partea a doua. 1 Entităţile limbajului Prolog Limbajul Prolog este un limbaj logic, descriptiv, care permite specificarea problemei de rezolvat în termenii unor fapte cunoscute despre obiectele universului problemei şi a relaţiilor existente între aceste obiecte. Execuţia unui program Prolog constă în deducerea implicaţiilor acestor fapte şi relaţii, programul definind astfel o mulţime de consecinţe ce reprezintă înţelesul sau semnificaţia declarativă a programului. Un program Prolog conţine următoarele entităţi: • fapte despre obiecte şi relaţiile existente între aceste obiecte; • reguli despre obiecte şi relaţiile dintre ele, care permit deducerea (inferarea) de noi fapte pe baza celor cunoscute; • întrebări, numite şi scopuri, despre obiecte şi relaţiile dintre ele, la care programul răspunde pe baza faptelor şi regulilor existente. 1.1 Fapte Faptele sunt predicate de ordinul întâi de aritate n considerate adevărate. Ele stabilesc relaţii între obiectele universului problemei. Numărul de argumente ale faptelor este dat de aritatea (numărul de argumente) corespunzătoare a predicatelor. Exemple:
  • 4. 4 Interpretarea particulară a predicatului şi a argumentelor acestuia depinde de programator. Ordinea argumentelor, odată fixată, este importantă şi trebuie păstrată la orice altă utilizare a faptului, cu aceeaşi semnificaţie. Mulţimea faptelor unui program Prolog formează baza de cunoştinţe Prolog. Se va vedea mai târziu că în baza de cunoştinte a unui program Prolog sunt incluse şi regulile Prolog. 1.2 Scopuri Obţinerea consecinţelor sau a rezultatului unui program Prolog se face prin fixarea unor scopuri care pot fi adevărate sau false, în funcţie de conţinutul bazei de cunoştinţe Prolog. Scopurile sunt predicate pentru care se doreşte aflarea valorii de adevăr în contextul faptelor existente în baza de cunoştinţe. Cum scopurile pot fi văzute ca întrebări, rezultatul unui program Prolog este răspunsul la o întrebare (sau la o conjuncţie de întrebări). Acest răspuns poate fi afirmativ, yes, sau negativ, no. Se va vedea mai târziu că programul Prolog, în cazul unui răspuns afirmativ la o întrebare, poate furniza şi alte informaţii din baza de cunoştinţe. Exemplu Considerând baza de cunoştinţe specificată anterior, se pot pune diverse întrebări, cum ar fi: ?- iubeste(mihai, maria). yes deoarece acest fapt există în baza de cunoştinţe ?- papagal(coco). yes ?- papagal(mihai). no deoarece acest fapt nu există în baza de cunoştinţe ?- inalt(gelu). no 1.3 Variabile În exemplele prezentate până acum, argumentele faptelor şi întrebărilor au fost obiecte particulare, numite şi constante sau atomi simbolici. Predicatele Prolog, ca orice predicate în logica cu predicate de ordinul I, admit ca argumente şi obiecte generice numite variabile. În Prolog, prin convenţie, numele argumentelor variabile începe cu literă iar numele constantelor simbolice începe cu literă mică. O variabilă poate fi instanţiată (legată) dacă Fapt: Aritate: papagal(coco). 1 iubeşte(mihai, maria). 2 iubeşte(mihai, ana). 2 frumoasă(ana). 1 bun(gelu). 1 deplasează(cub, camera1, camera2). 3
  • 5. 5 există un obiect asociat acestei variabile, sau neinstanţiată (liberă) dacă nu se ştie încă ce obiect va desemna variabila. La fixarea unui scop Prolog care conţine variabile, acestea sunt neinstanţiate iar sistemul încearcă satisfacerea acestui scop căutând printre faptele din baza de cunoştinţe un fapt care poate identifica cu scopul, printr-o instanţiere adecvată a variabilelor din scopul dat. Este vorba de fapt de un proces de unificare a predicatului scop cu unul din predicatele fapte existente în baza de cunoştinţe. La încercarea de satisfacere a scopului, căutarea se face întotdeauna pornind de la începutul bazei de cunoştinţe. Dacă se întâlneşte un fapt cu un simbol predicativ identic cu cel al scopului, variabilele din scop se instanţiază conform algoritmului de unificare şi valorile variabilelor astfel obţinute sunt afişate ca răspuns la satisfacerea acestui scop. Exemple: ?- papagal(CineEste). CineEste = coco ?- deplaseaza(Ce, DeUnde, Unde). Ce = cub, DeUnde = camera1, Unde = camera2 ?- deplaseaza(Ce, Aici, Aici). no Cum se comportă sistemul Prolog în cazul în care există mai multe fapte în baza de cunoştinţe care unifică cu întrebarea pusă? În acest caz există mai multe răspunsuri la întrebare, corespunzând mai multor soluţii ale scopului fixat. Prima soluţie este dată de prima unificare şi există atâtea soluţii câte unificări diferite există. La realizarea primei unificări se marchează faptul care a unificat şi care reprezintă prima soluţie. La obţinerea următoarei soluţii, căutarea este reluată de la marcaj în jos în baza de cunoştinţe. Obţinerea primei soluţii este de obicei numită satisfacerea scopului iar obţinerea altor soluţii, resatisfacerea scopului. La satisfacera unui scop căutarea se face întotdeauna de la începutul bazei de cunoştinţe. La resatisfacerea unui scop, căutarea se face începând de la marcajul stabilit de satisfacerea anterioară a acelui scop. Sistemul Prolog, fiind un sistem interactiv, permite utilizatorului obţinerea fie a primului răspuns, fie a tuturor răspunsurilor. În cazul în care, după afişarea tuturor răspunsurilor, un scop nu mai poate fi resatisfăcut, sistemul răspunde no. Exemple: ?- iubeste(mihai, X). X = maria; tastând caracterul “;” şi Enter, cerem o nouă soluţie X = ana; no ?- iubeste(Cine, PeCine).
  • 6. 6 Cine = mihai, PeCine = maria; Cine = mihai, PeCine = ana; no Există deci două soluţii pentru scopul iubeste(mihai, X) şi tot două soluţii pentru scopul iubeste(Cine, PeCine), considerând tot baza de cunoştinţe prezentată în secţiunea 1.1. 1.4 Reguli O regulă Prolog exprimă un fapt care depinde de alte fapte şi este de forma: S :- S1, S2, …Sn. cu semnificaţia prezentată la începutul acestui capitol. Fiecare Si, i = 1,n şi S au forma faptelor Prolog, deci sunt predicate, cu argumente constante, variabile sau structuri. Faptul S care defineşte regula, se numeşte antet de regulă, iar S1, S2, …Sn formează corpul regulii şi reprezintă conjuncţia de scopuri care trebuie satisfăcute pentru ca antetul regulii să fie satisfăcut. Fie următoarea bază de cunoştinţe Prolog: frumoasa(ana). % 1 bun(vlad). % 2 cunoaste(vlad, maria). % 3 cunoaste(vlad, ana). % 4 iubeste(mihai, maria). % 5 iubeste(X, Y) :- % 6 bun(X), cunoaste(X, Y), frumoasa(Y). Se observă că enunţul (6) defineşte o regulă Prolog; relaţia iubeste(Cine, PeCine), fiind definită atât printr-un fapt (5) cât şi printr-o regulă (6). În condiţiile existenţei regulilor în baza de cunoştinţe Prolog, satisfacerea unui scop se face printr-un procedeu similar cu cel prezentat în Secţiunea 1.2, dar unificarea scopului se încearcă atât cu fapte din baza de cunoştinţe, cât şi cu antetul regulilor din bază. La unificarea unui scop cu antetul unei reguli, pentru a putea satisface acest scop trebuie satisfăcută regula. Aceasta revine la a satisface toate faptele din corpul regulii, deci conjuncţia de scopuri. Scopurile din corpul regulii devin subscopuri a căror satisfacere se va încerca printr-un mecanism similar cu cel al satisfacerii scopului iniţial. Pentru baza de cunoştinţe descrisă mai sus, satisfacerea scopului ?- iubeste(vlad, ana).
  • 7. 7 se va face în următorul mod. Scopul unifică cu antetul regulii (6) şi duce la instanţierea variabilelor din regula (6): X = vlad şi Y = ana. Pentru ca acest scop să fie îndeplinit, trebuie îndeplinită regula, deci fiecare subscop din corpul acesteia. Aceasta revine la îndeplinirea scopurilor bun(vlad), care reuşeşte prin unificare cu faptul (2), cunoaste(vlad, ana), care reuşeşte prin unificare cu faptul (4), şi a scopului frumoasa(ana), care reuşeşte prin unificare cu faptul (1). În consecinţă, regula a fost îndeplinită, deci şi întrebarea iniţială este adevarată, iar sistemul răspunde yes. Ce se întîmplă dacă se pune întrebarea: ?- iubeste(X, Y). Prima soluţie a acestui scop este dată de unificarea cu faptul (5), iar răspunsul este: X = mihai, Y = maria Sistemul Prolog va pune un marcaj în dreptul faptului (5) care a satisfăcut scopul. Următoarea soluţie a scopului iubeste(X, Y) se obţine începând căutarea de la acest marcaj în continuare în baza de cunoştinţe. Scopul unifică cu antetul regulii (6) şi se vor fixa trei noi subscopuri de îndeplinit, bun(X), cunoaste(X, Y) şi frumoasa(Y). Scopul bun(X) este satisfăcut de faptul (2) şi variabila X este instanţiată cu valoarea vlad, X = vlad. Se încearcă acum satisfacerea scopului cunoaste(vlad, Y), care este satisfăcut de faptul (3) şi determină instanţierea Y = maria. Se introduce în baza de cunoştinţe un marcaj asociat scopului cunoaste(vlad, Y), care a fost satisfăcut de faptul (3). Se încearcă apoi satisfacerea scopului frumoasa(maria). Acesta eşuează. În acest moment sistemul intră într-un proces de backtracking în care se încearcă resatisfacerea scopului anterior satisfăcut, cunoaste(vlad, Y), în speranţa că o noua soluţie a acestui scop va putea satisface şi scopul curent care a eşuat, frumoasa(Y). Resatisfacerea scopului cunoaste(vlad, Y) se face pornind căutarea de la marcajul asociat scopului în jos, deci de la faptul (3) în jos. O nouă soluţie (resatisfacere) a scopului cunoaste(vlad, Y) este dată de faptul (4) care determină instanţierea Y = ana. În acest moment se încearcă satisfacerea scopului frumoasa(ana). Cum este vorba de un nou scop, căutarea se face de la începutul bazei de cunoştinţe şi scopul frumoasa(ana) este satisfăcut de faptul (1). În consecinţă a doua soluţie a scopului iubeste(X, Y) este obţinută şi sistemul răspunde: X = vlad, Y = ana urmând un mecanism de backtracking, descris intuitiv în figura 1 prin prezentarea arborilor de deducţie construiţi de sistemul Prolog. La încercarea de resatisfacere a scopului iubeste(X, Y), printr-un mecanism similar, se observă că nu mai există alte solutii. În concluzie, fiind dată baza de fapte şi reguli Prolog anterioară, comportarea sistemului Prolog este: ?- iubeste(X, Y).
  • 8. 8 X = mihai, Y = maria; X = vlad, Y = ana; no Figura 1. Mecanismul de satisfacere a scopurilor în Prolog Observaţii: • La satisfacerea unei conjuncţii de scopuri în Prolog, se încearcă satisfacerea fiecărui scop pe rând, de la stânga la dreapta. Prima satisfacere a unui scop determină plasarea unui marcaj în baza de cunoştinţe în dreptul faptului sau regulii care a determinat satisfacerea scopului. • Dacă un scop nu poate fi satisfăcut (eşuează), sistemul Prolog se întoarce şi încearcă resatisfacerea scopului din stânga, pornind căutarea în baza de cunoştinţe de la marcaj în jos. Înainte de resatisfacerea unui scop se elimină toate instanţierile de variabile determinate de ultima satisfacere a acestuia. Dacă cel mai din stânga scop din conjuncţia de scopuri nu poate fi satisfăcut, întreaga conjuncţie de scopuri eşuează. iubeste(X,Y) %5 iubeste(mihai,maria) SUCCES solutie 1: X=mihai, Y=maria iubeste(X,Y) bun(X) %2 bun(vlad) SUCCES cunoaste(X,Y) %3 cunoaste(vlad,maria) %6 frumoasa(Y) frumoasa(maria) %4 cunoaste(vlad,ana) %1 frumoasa(ana) solutie 2: X=vlad, Y=ana cunoaste(vlad,Y) SUCCES SUCCES SUCCESINSUCCES
  • 9. 9 • Această comportare a sistemului Prolog în care se încearcă în mod repetat satisfacerea şi resatisfacerea scopurilor din conjuncţiile de scopuri se numeşte backtracking. • În secţiunea 4 se va discuta pe larg structura de control a sistemului Prolog, mecanismul fundamental de backtracking şi modurile în care se poate modifica parţial acest mecanism. 1.5 Un program Prolog simplu Simplitatea şi expresivitatea limbajului Prolog poate fi pusă în evidentă de următorul exemplu de descriere a unui circuit logic "poartă ŞI". Se consideră poarta ŞI ca fiind construită prin conectarea unei "porţi ŞI-NU" cu un inversor. Întregul circuit este definit de relaţia: poarta_si(Intrare1, Intrare2, Iesire) pe baza relaţiilor poarta_si_nu(Intrare1, Intrare2, Iesire) inversor(Intrare, Iesire). În figura 2 este prezentată schema porţii ŞI în care se observă că inversorul este construit dintr-un tranzistor cu sursa conectată la masă şi un rezistor cu un capăt conectat la alimentare. Poarta tranzistorului este intrarea inversorului, în timp ce celălalt capăt al rezistorului trebuie conectat la colectorul tranzistorului care formează ieşirea inversorului. Figura 2. Un circuit logic poartă ŞI Variabilele comune între predicate sunt utilizate pentru a specifica legăturile comune. rezistor(alimentare, n1). rezistor(alimentare, n2). n1 n2 n3 n4 n5
  • 10. 10 tranzistor(n2, masa, n1). tranzistor(n2, masa, n1). tranzistor(n3, n4, n2). tranzistor(n5, masa, n4). inversor(Intrare, Iesire) :- tranzistor(Intrare, masa, Iesire), rezistor(alimentare, Iesire). poarta_si_nu(Intrare1, Intrare2, Iesire) :- tranzistor(Intrare1, X, Iesire), tranzistor(Intrare2, masa, X), rezistor(alimentare, Iesire). poarta_si(Intrare1, Intrare2, Iesire) :- poarta_si_nu(Intrare1, Intrare2, X), inversor(X, Iesire). Pentru întrebarea ?- poarta_si(In1, In2, Iesire). In1 = n3, In2= n5, Iesire = n1 răspunsul sistemului Prolog confirmă faptul că circuitul descris este o poartă ŞI, identificând intrările şi ieşirile corespunzătoare. 2 Sintaxa limbajului Prolog Aşa cum s-a arătat în secţiunea anterioară, un program Prolog este format din fapte, reguli şi întrebări, acestea fiind construite pe baza predicatelor definite de utilizator sau predefinite. În orice sistem Prolog există o mulţime de predicate predefinite, unele dintre acestea fiind predicate standard, iar altele depinzând de implementare. Argumentele predicatelor Prolog, prin analogie cu logica predicatelor de ordinul I, se numesc termeni, şi pot fi constante, variabile sau structuri. 2.1 Constante Constantele definesc obiecte specifice, particulare, sau relaţii particulare. Există două tipuri de constante: atomi şi numere. Atomii sunt constante simbolice care încep, de obicei, cu o literă şi pot conţine litere, cifre şi caracterul “_”. Există şi alte caractere ce pot forma atomi speciali, care au o semnificaţie aparte în limbaj. Atomii pot desemna: • obiecte constante care sunt argumentele predicatelor, de exemplu atomii mihai şi maria în faptul iubeste(mihai, maria); • predicate Prolog, fie cele definite de utilizator, fie cele predefinite în sistem; de exemplu atomul iubeste în faptul iubeste(mihai, maria);
  • 11. 11 • atomi speciali, de exemplu atomii “:-” şi “?”- ; • diverse alte reguli de construcţie sintactică a atomilor depind de implementare. Numerele pot fi întregi sau reale; sintaxa particulară acceptată cât şi domeniile de definiţie depinzând de implementare. Detalii despre formatul numerelor şi a altor tipuri de constante din mediul ARITY Prolog sunt descrise în partea a doua. 2.2 Variabile Variabilele sunt, din punct de vedere sintactic, tot atomi, dar ele au o semnificaţie specială, aşa cum s-a arătat în Secţiunea 1.3. Spre deosebire de regulile generale admise pentru construcţia atomilor, numele unei variabile poate începe şi cu simbolul “_”, ceea ce indică o variabilă anonimă. Utilizarea unei variabile anonime semnifică faptul că nu interesează valoarea la care se va instanţia acea variabilă. De exemplu, interogarea ?- iubeste( _, maria). semnifică faptul că se întreabă dacă există cineva care o iubeşte pe Maria, dar nu interesează cine anume. Limbajul Prolog face distincţia între litere mari şi litere mici (este case sensitive). Se reaminteşte că, din punctul de vedere al convenţiei Prolog, numele oricărei variabile trebuie să înceapă fie cu literă mare, fie cu “_”. 2.3 Structuri O structură Prolog este un obiect ce desemneaza o colecţie de obiecte corelate logic, care formează componentele structurii. Un exemplu este structura asociată obiectului carte, care este formată din componentele: titlu carte, autor, şi an apariţie. Un fapt ce referă relaţia de posedare a unei cărţi de Prolog de către Mihai poate fi exprimat astfel: poseda(mihai, carte(prolog, clocksin, 1981)). unde carte(prolog, clocksin, 1981) este o structură cu numele carte şi cu trei componente: prolog, clocksin şi 1981. Se admit şi structuri imbricate, de exemplu: poseda(mihai, carte(prolog, autori(clocksin, mellish), 1981)). unde predicatul poseda are două argumente: primul argument este constanta mihai, iar cel de-al doilea este structura carte(prolog ....), cu două componente, a doua componentă fiind structura autori(clocksin, mellish). În Prolog, o structură se defineşte prin specificarea: (1) numelui structurii ( functorul structurii); (2) elementelor structurii (componentele structurii). Structurile Prolog pot fi utilizate pentru reprezentarea structurilor de date, de exemplu liste sau arbori. În Secţiunea 2.6 se vor prezenta structuri Prolog predefinite care permit
  • 12. 12 lucrul cu liste. Un arbore binar poate fi reprezentat în Prolog tot printr-o structură, de exemplu: arb(barbu, arb(ada, vid, vid), vid). reprezintă un arbore binar cu cheia din rădăcina barbu, cu subarborele drept vid şi cu subarborele stâng format dintr-un singur nod cu cheia ada. Observaţii: • Sintaxa structurilor este aceeaşi cu cea a faptelor Prolog. Un predicat Prolog poate fi văzut ca o structură a cărui functor este numele predicatului, iar argumentele acestuia reprezintă componentele structurii. • Considerarea predicatelor Prolog ca structuri prezintă un interes deosebit; atât datele cât şi programele în Prolog au aceeaşi formă, ceea ce facilitează tratarea uniformă şi sinteza dinamică de programe. În Secţiunea 4.3 se vor prezenta predicate predefinite în Prolog care permit sinteza şi execuţia dinamică a programelor Prolog. 2.4 Operatori Uneori este convenabil să se scrie anumiţi functori (nume de structuri sau predicate) în formă infixată. Aceasta este o formă sintactică ce măreşte claritatea programului, cum ar fi cazul operatorilor aritmetici sau al operatorilor relaţionali. Limbajul Prolog oferă o mulţime de operatori, unii care se regăsesc în aproape orice implementare, iar alţii care sunt specifici unei versiuni particulare de implementare a limbajului. În continuare se vor prezenta o parte dintre operatorii din prima categorie. (1) Operatori aritmetici Operatorii aritmetici binari, cum ar fi +, -, *, /, pot fi scrişi în Prolog în notaţie infixată; de exemplu: 1 + 2*(X * Y) / Z Aceasta sintaxa este de fapt o rescriere infixată a formei prefixate a structurilor echivalente: +(1, 2) / (*(X, Y), Z) Este important de reţinut că operatorii aritmetici sunt o rescriere infixată a unor structuri deoarce valoarea expresiei astfel definită nu este calculată. Evaluarea expresiei se face la cerere în cazul în care se foloseste operatorul predefinit infixat is, de exemplu: X is 1 + 2. va avea ca efect instanţierea variabilei X la valoarea 3. (2) Operatori relaţionali
  • 13. 13 Operatorii relaţionali sunt predicate predefinite infixate. Un astfel de operator este operatorul de egalitate =. Predicatul (operatorul) de egalitate funcţionează ca şi cum ar fi definit prin următorul fapt: X = X. iar încercarea de a satisface un scop de tipul X = Y se face prin încercarea de a unifica X cu Y. Din aceasta cauză, dându-se un scop de tipul X = Y, regulile de decizie care indică dacă scopul se îndeplineşte sau nu sunt următoarele: • Dacă X este variabilă neinstanţiată, iar Y este instanţiată la orice obiect Prolog, atunci scopul reuşeşte. Ca efect lateral, X se va instanţia la aceeaşi valoare cu cea a lui Y. De exemplu: ?- carte(barbu, poezii) = X. este un scop care reuşeşte şi X se instanţiază la carte(barbu, poezii). • Dacă atât X cât şi Y sunt variabile neinstanţiate, scopul X = Y reuşeşte, variabila X este legată la Y şi reciproc. Aceasta înseamnă că ori de câte ori una dintre cele două variabile se instanţiază la o anumită valoare, cealaltă variabila se va instanţia la aceeaşi valoare. • Atomii şi numerele sunt întotdeauna egali cu ei înşişi. De exemplu, următoarele scopuri au rezultatul marcat drept comentariu: mihai = mihai % este satisfăcut mare = mic % eşuează 102 = 102 % reuşeşte 1010 = 1011 % eşuează • Două structuri sunt egale dacă au acelaşi functor, acelaşi număr de componente şi fiecare componentă dintr-o structură este egală cu componenta corespunzătoare din cealaltă structură. De exemplu, scopul: autor(barbilian, ciclu(uvedenrode, poezie(riga_crypto))) = autor(X, ciclu(uvedenrode, poezie(Y))) este satisfăcut, iar ca efect lateral se fac instanţierile: X = barbilian şi Y = riga_crypto. Operatorul de inegalitate = se defineşte ca un predicat opus celui de egalitate. Scopul X = Y reuşeşte dacă scopul X = Y nu este satisfăcut şi eşuează dacă X = Y reuşeşte. În plus, există predicatele relaţionale de inegalitate definite prin operatorii infixaţi >, <, =<, >=, cu semnificaţii evidente.
  • 14. 14 Un operator interesant este =:=. El face numai evaluare aritmetică şi nici o instanţiere. Exemplu: ?- 1 + 2 =:= 2 + 1. yes ?- 1 + 2 = 2 + 1. no Predicatul = = testează echivalenţa a două variabile. El consideră cele două variabile egale doar dacă ele sunt deja partajate. X = = Y reuşeşte ori de câte ori X = Y reuşeşte, dar reciproca este falsă: ?- X = = X. X=_23 %variabilă neinstanţiată ?- X= =Y. no ?- X=Y, X= =Y. X=_23, Y=_23 % X şi Z variabile neinstanţiate partajate În cele mai multe implementări Prolog există predefinit operatorul de obţinere a modulului unui număr, mod; scopul X is 5 mod 3. reuşeşte şi X este instanţiat la 2. Comentariile dintr-un program Prolog sunt precedate de caracterul “%”. Exemple: 1. Presupunând că nu există operatorul predefinit mod, se poate scrie un predicat Prolog cu efect similar. O definiţie posibilă a predicatului modulo(X, Y, Z), cu semnificaţia argumentelor Z = X mod Y , presupunând X, Y > 0, este: % modulo(X, Y, Z) modulo(X, Y, X) :- X < Y. modulo(X, Y, Z) :- X >= Y, X1 is X - Y, modulo(X1, Y, Z). 2. Plecând de la predicatul modulo definit anterior, se poate defini predicatul de calcul al celui mai mare divizor comun al două numere, conform algoritmului lui Euclid, presupunând X > 0, Y > 0, astfel: % cmmdc(X, Y, C) cmmdc(X, 0, X). cmmdc(X, Y, C) :- modulo(X, Y, Z), cmmdc(Y, Z, C). La întrebarea
  • 15. 15 ?- cmmdc(15, 25, C). C=5 răspunsul sistemului este corect. În cazul în care se încearcă obţinerea unor noi soluţii (pentru semnificaţia cmmdc acest lucru este irelevant, dar interesează din punctul de vedere al funcţionării sistemului Prolog) se observă ca sistemul intră într-o buclă infinită datorita imposibilităţii resatisfacerii scopului modulo(X, Y, Z) pentru Y = 0. Dacă la definiţia predicatului modulo se adaugă faptul: modulo(X, 0, X). atunci predicatul modulo(X, Y, Z) va genera la fiecare resatisfacere aceeaşi soluţie, respectiv solutia corectă, la infinit. Cititorul este sfătuit să traseze execuţia predicatului cmmdc în ambele variante de implementare a predicatului modulo. 3. Calculul ridicării unui număr natural la o putere naturală se poate face definind următorul predicat: % expo(N, X, XlaN) expo( _ , 0, 0). expo(0, _ , 1). expo(N, X, Exp) :- N > 0, N1 is N - 1, expo(N1, X, Z), Exp is Z * X. 2.5 Operatori definiţi de utilizator Limbajul Prolog permite definirea de noi operatori de către programator prin introducerea în program a unor clauze de formă specială, numite directive. Directivele acţionează ca o definire de noi operatori ai limbajului în care se specifică numele, precedenţa şi tipul (infixat, prefixat sau postfixat) operatorului. Se reaminteşte faptul că orice operator Prolog este de fapt o structură care are ca functor numele operatorului şi ca argumente argumentele operatorului, dar este scris într-o sintaxă convenabilă. La definirea de noi operatori în Prolog, se creează de fapt noi structuri cărora le este permisă o sintaxă specială, conform definiţiei corespunzătoare a operatorului. Din acest motiv, nu se asociază nici o operaţie operatorilor definiţi de programator. Operatorii noi definiţi sunt utilizaţi ca functori numai pentru a combina obiecte în structuri şi nu pentru a executa acţiuni asupra datelor, deşi denumirea de operator ar putea sugera o astfel de acţiune. De exemplu, în loc de a utiliza structura: are(coco, pene) se poate defini un nou operator are :- op(600, xfx, are). caz în care este legală expresia
  • 16. 16 coco are pene. Definirea de noi operatori se face cu ajutorul directivei: :- op(precedenţă-operator, tip-operator, nume-operator). Operatorii sunt atomi iar precedenta lor trebuie să fie o valoare întreagă într-un anumit interval şi corelată cu precedenţa operatorilor predefiniţi în limbaj. Tipul operatorilor fixează caracterul infixat, prefixat sau postfixat al operatorului şi precedenţa operanzilor săi. Tipul operatorului se defineşte utilizând una din următoarele forme standard: (1) operatori infixaţi: xfx xfy yfx (2) operatori prefixaţi: fx fy (3) operatori postfixaţi: xf yf unde f reprezintă operatorul, iar x şi y operanzii săi. Utilizarea simbolului x sau a simbolului y depinde de precedenţa operandului faţă de operator. Precedenţa operanzilor se defineşte astfel: • un argument între paranteze sau un argument nestructurat are precedenţa 0; • un argument de tip structură are precedenţa egală cu cea a functorului operator. Observaţie: În ARITY Prolog, cu cât valoare-precedenţă-operator este mai mare, cu atât operatorul are o precedenţă mai mică şi invers. Semnificaţiile lui x şi y în stabilirea tipului operatorului sunt următoarele: • x reprezintă un argument (operand) cu precedenţă strict mai mică decât cea a functorului (operatorului) f precedenţa( x ) < precedenţa( f ) • y reprezintă un argument (operand) cu precedenţă mai mică sau egală cu cea a functorului (operandului) f precedenţa( y ) ≤ precedenţa( f ) Aceste reguli ajută la eliminarea ambiguităţii operatorilor multipli cu aceeaşi precedenţă. De exemplu, operatorul predefinit în Prolog - (minus) este definit din punct de vedere al tipului ca yfx, ceea ce înseamnă că structura a - b -c este interpretată ca (a - b) -c şi nu ca a -(b -c). Dacă acest operator ar fi fost definit ca xfy, atunci interpretarea structurii a - b -c ar fi fost a -(b -c). Numele operatorului poate fi orice atom Prolog care nu este deja definit în Prolog. Se poate folosi şi o listă de atomi, dacă se definesc mai mulţi operatori cu aceeaşi precedenţă şi acelaşi tip. Exemple:
  • 17. 17 :- op(100, xfx, [este, are]). :- op(100, xf, zboara). coco are pene. coco zboara. coco este papagal. bozo este pinguin. ?- Cine are pene. Cine = coco ?- Cine zboara. Cine = coco ?- Cine este Ce. Cine = coco, Ce = papagal; Cine = bozo, Ce = pinguin; no În condiţiile în care se adaugă la baza de cunoştinţe anterioară şi definiţia operatorilor daca, atunci şi si :- op(870, fx, daca). :- op(880, xfx, atunci). :- op(880, xfy, si). următoarea structură este validă în Prolog: daca Animalul are pene şi Animalul zboara atunci Animalul este pasare. 2.6 Liste O listă este o structură de date ce reprezintă o secvenţă ordonată de zero sau mai multe elemente. O listă poate fi definită recursiv astfel: (1) lista vidă (lista cu 0 elemente) este o listă (2) o listă este o structură cu două componente: primul element din listă (capul listei) şi restul listei (lista formată din urmatoarele elemente din lista). Sfârşitul unei liste este de obicei reprezentat ca lista vidă. În Prolog structura de listă este reprezentată printr-o structură standard, predefinită, al cărei functor este caracterul “.” şi are două componente: primul element al listei şi restul listei. Lista vidă este reprezentată prin atomul special [ ]. De exemplu, o listă cu un singur element a se reprezintă în Prolog, prin notaţie prefixată astfel: .(a, [ ]) având sintaxa in ARITY Prolog '.'(a, [ ])
  • 18. 18 iar o listă cu trei elemene, a, b, c, se reprezintă ca: .(a, . (b, . (c, [ ]))) cu sintaxa în ARITY Prolog '.'(a, '. '(b, '. '(c, [ ]))). Deoarece structura de listă este foarte des utilizată în Prolog, limbajul oferă o sintaxă alternativă pentru descrierea listelor, formată din elementele listei separate de virgulă şi încadrate de paranteze drepte. De exemplu, cele două liste anterioare pot fi exprimate astfel: [a] [a, b, c] Această sintaxă a listelor este generală şi valabilă în orice implementare Prolog. O operaţie frecventă asupra listelor este obţinerea primului element dintr-o listă şi a restului listei, deci a celor două componente ale structurii de listă. Aceasta operaţie este realizată în Prolog de operatorul de scindare a listelor “|” scris sub următoarea formă: [Prim | Rest] Variabila Prim stă pe postul primului element din listă, iar variabila Rest pe postul listei care conţine toate elementele din listă cu excepţia primului. Acest operator poate fi aplicat pe orice listă care conţine cel puţin un element. Dacă lista conţine exact un element, Rest va reprezenta lista vidă. Încercarea de identificare a structurii [Prim | Rest] cu o listă vidă duce la eşec. Mergând mai departe, se pot obţine chiar primele elemente ale listei şi restul listei. Iată câteva echivalenţe: [a, b, c] = [a | [b, c] ] = [a, b | [c] ] = [a, b, c | [ ] ] = [a | [b | [c] ] ] = [a | [b | [c | [ ] ] ] ]. În Prolog elementele unei liste pot fi atomi, numere, liste şi în general orice structuri. În consecinţă se pot construi liste de liste. Exemple: 1. Se poate defini următoarea structură de listă: [carte(barbu, poezii), carte(clocksin, prolog)] 2. Considerând următoarele fapte existente în baza de cunoştinţe Prolog pred([1, 2, 3, 4]). pred([coco, sta, pe, [masa, alba]]). se pot pune următoarele întrebări obţinând răspunsurile specificate: ?- pred([Prim | Rest]). Prim = 1, Rest = [2, 3, 4]; Prim = coco, Rest = [sta, pe, [masa, alba]]; no ?- pred([ _, _ , _ , [ _ | Rest]])
  • 19. 19 Rest = [alba] 3. Un predicat util în multe aplicaţii este cel care testează apartenenţa unui element la o listă şi care se defineşte astfel: % membru(Element, Lista) membru(Element, [Element | _ ]). membru(Element, [ _ | RestulListei]) :- membru(Element, RestListei). Funcţionarea acestui scurt program Prolog poate fi urmărită cerând răspunsul sistemului la următoarele scopuri: ?- membru(b, [a, b, c]). %1 yes ?- membru(X, [a, b, c]). %2 X = a; X = b; X = c; no ?- membru(b, [a, X, b]). %3 X = b; X = _0038; no Deci pentru cazul în care primul argument este o variabilă (%2) există trei soluţii posibile ale scopului membru(Element, Lista). Dacă lista conţine o variabilă (%3) există două soluţii pentru forma listei ([a, b, b] sau [a, _ , b]). 4. Un alt predicat util este cel de concatenare a două liste L1 şi L2, rezultatul concatenării obţinându-se în lista L3, iniţial neinstanţiată. % conc(L1, L2, L3) conc([], L2, L2). % lista vidă concatenată cu L2 este L2. conc([Prim1|Rest1], Lista2, [Prim1|Rest3]) :- conc(Rest1, Lista2, Rest3).% lista rezultată este primul element % al sublistei curente din L1 concatenat % cu lista întoarsă de apelul recursiv. ?- conc([a, b], [c, d, e], L3). L3 = [a, b, c, d, e]; no ?- conc([a, b], [c, d, e], L3). L3 = [a, b, c, d, e]. % “.” şi Enter când nu căutăm alte soluţii yes ?- conc(L1, [c, d, e], [a, b, c, d, e]). L1 = [a, b];
  • 20. 20 no ?- conc([a, b], L2, [a, b, c, d, e]). L2 = [c, d, e]; no ?- conc(L1, L2, [a, b]). L1 = [ ], L2 = [a, b]; L1 = [a], L2 = [b]; L1 = [a, b], L2 = [ ]; no Se observă că pentru cazul în care predicatul de concatenare are un singur argument neinstanţiat există o singură soluţie, iar pentru cazul în care primele două argumente sunt neinstanţiate (variabile) se obţin mai multe soluţii, corespunzătoare tuturor variantelor de liste care prin concatenare generează cea de a treia listă. 5. Următorul exemplu defineşte predicatul de testare a existenţei unei sortări în ordine crescătoare a elementelor unei liste de întregi. Predicatul reuşeşte dacă elementele listei sunt sortate crescător şi eşuează în caz contrar. % sortcresc(Lista) sortcresc([ ]). % lista vidă se consideră sortată sortcresc([ _ ]). % lista cu un singur element este sortată sortcresc([X, Y | Rest]) :- X = < Y, sortcresc([Y | Rest]). ?- sortcresc([1, 2, 3, 4]). yes ?- sortcresc([1, 3, 5, 4]). no ?- sortcresc([ ]). yes Dacă se consideră că lista vidă nu este o listă sortată crescător atunci se poate elimina faptul: sortcresc([ ]). din definiţia predicatului sortcresc, comportarea lui pentru liste diferite de lista vidă rămânând aceeaşi. Observaţii: • Exemplul 3 pune în evidenţă o facilitate deosebit de interesantă a limbajului Prolog, respectiv puterea generativă a limbajului. Predicatul membru poate fi utilizat atât pentru testarea apartenenţei unui element la o listă, cât şi pentru generarea, pe rând,
  • 21. 21 a elementelor unei liste prin resatisfacere succesivă. În anumite contexte de utilizare această facilitate poate fi folositoare, iar în altele ea poate genera efecte nedorite atunci când predicatul membru este utilizat în definirea altor predicate, aşa cum se va arăta în partea a doua a lucrării. • Aceeaşi capacitate generativă a limbajului Prolog poate fi observată şi în Exemplul 4 unde în funcţie de combinaţiile de argumente instanţiate şi neinstanţiate, predicatul conc poate produce rezultate relativ diferite. • La definirea unui predicat p care va fi utilizat în definirea altor predicate trebuie întotdeauna să se analizeze numărul de soluţii şi soluţiile posibile ale predicatului p. Acest lucru este necesar deoarece dacă p apare într-o conjuncţie de scopuri p1,…, pi-1, p ,pi+1,…, pn şi unul dintre scopurile pi+1,…, pn eşuează, mecanismul de backtracking din Prolog va încerca resatisfacerea scopului p. Numărul de soluţii şi soluţiile scopului p influenţează astfel, în mod evident, numărul de soluţii şi soluţiile conjuncţiei de scopuri p1,…, pi-1, p ,pi+1,…, pn. Exemple în acest sens şi modalităţi de reducere a numărului total de soluţii ale unui corp vor fi prezentate în Secţiunea 4.2. 3 Limbajul Prolog şi logica cu predicate de ordinul I Limbajul Prolog este un limbaj de programare logică. Deşi conceput iniţial pentru dezvoltarea unui interpretor de limbaj natural, limbajul s-a impus ca o soluţie practică de construire a unui demonstrator automat de teoreme folosind rezoluţia. Demonstrarea teoremelor prin metoda rezoluţiei necesită ca axiomele şi teorema să fie exprimate în forma clauzală, adică o disjuncţie de literali, unde un literal este un predicat sau un predicat negat. Pentru detalii despre noţiunea de clauză şi modul de transformare a unei formule din logica cu predicate de ordinul I în formă clauzală se poate consulta [Flo93]. Sintaxa şi semantica limbajului Prolog permit utilizarea numai a unei anumite forme clauzale a formulelor bine formate: clauze Horn distincte. Deoarece faptele şi regulile Prolog sunt în formă clauzală, forma particulară a clauzelor fiind clauze Horn distincte, ele se mai numesc şi clauze Prolog. Definiţie. Se numeşte clauză Horn o clauză care conţine cel mult un literal pozitiv. O clauză Horn poate avea una din următoarele patru forme: (1) o clauză unitară pozitivă formată dintr-un singur literal pozitiv (literal nenegat); (2) o clauză negativă formată numai din literali negaţi; (3) o clauză formată dintr-un literal pozitiv şi cel puţin un literal negativ, numită şi clauză Horn mixtă;
  • 22. 22 (4) clauză vidă. Definiţie. Se numeşte clauză Horn distinctă o clauză care are exact un literal pozitiv, ea fiind fie o clauză unitară pozitivă, fie o clauză Horn mixtă. Clauzele Horn unitare pozitive se reprezintă în Prolog prin fapte, iar clauzele Horn mixte prin reguli. O clauză Horn mixtă de forma: ∼S1 ∨ ∼S2 ∨…∨ ∼Sn ∨ S se exprimă în Prolog prin regula: S :- S1, S2, …Sn. Semnificaţia intuitivă a unei reguli Prolog are un corespondent clar în logica cu predicate de ordinul I dacă se ţine cont de faptul că o clauză Horn mixtă poate proveni din următoarea formulă bine formată: S1 ∧ S2 ∧…∧ Sn → S Variabilele din clauzele distincte se transformă în variabile Prolog, constantele din aceste formule în constante Prolog, iar funcţiile pot fi asimilate cu structuri Prolog. Deci argumentele unui predicat Prolog au forma termenilor din calculul cu predicate de ordinul I. Exemple: 1. Fie următoarele enunţuri: Orice sportiv este puternic. Oricine este inteligent şi puternic va reuşi în viaţă. Oricine este puternic va reuşi în viaţă sau va ajunge bătăuş. Există un sportiv inteligent. Gelu este sportiv. Exprimând enunţurile în logica cu predicate de ordinul I se obţin următoarele formule bine formate: A1. (∀x) (sportiv(x) → puternic(x)) A2. (∀x) (inteligent(x) ∧ puternic(x) → reuşeşte(x)) A3. (∀x) (puternic(x) → (reuşeşte(x) ∨ bătăuş(x))) A4. (∃x) (sportiv(x) ∧ inteligent(x)) A5. Sportiv(gelu) Axiomele se transformă în forma clauzală şi se obţin următoarele clauze: C1. ∼ sportiv(x) ∨ puternic(x) C2. ∼ inteligent(x) ∨ ~ puternic(x) ∨ reuseste(x) C3. ~ puternic(x) ∨ reuseste(x) ∨ bataus(x) C4. sportiv(a) C4'. inteligent(a) C5. sportiv(gelu)
  • 23. 23 Clauzele C1, C2, C4, C4' şi C5 pot fi transformate în Prolog deoarece sunt clauze Horn distincte, dar clauza C3 nu poate fi transformată în Prolog. Programul Prolog care se obţine prin transformarea acestor clauze este următorul: puternic(X) :- sportiv(X). reuseste(X) :- inteligent(X), puternic(X). sportiv(a). inteligent(a). sportiv(gelu). 2. Fie următoarele enunţuri: (a) Orice număr raţional este un număr real. (b) Există un număr prim. (c) Pentru fiecare număr x există un număr y astfel încât x < y . Dacă se notează cu prim(x) “x este număr prim”, cu rational(x) ”x este număr raţional”, cu real(x) “x este număr real” şi cu mai_mic(x, y) “x este mai mic decât y“, reprezentarea sub formă de formule bine formate în calculul cu predicate de ordinul I este următoarea: A1. (∀x) (raţional(x) → real(x)) A2. (∃x) prim(x) A3. (∀x) (∃y) mai_mic(x, y) Reprezentarea în formă clauzală este: C1. ~ rational(x) ∨ real(x) C2. prim(a) C3. mai_mic(x, mai_mare(x)) unde mai_mare(x) este funcţia Skolem care înlocuieşte variabila y cuantificată existenţial. Forma Prolog echivalentă a acestor clauze este: real(X) :- rational(X). prim(a). mai_mic(X, mai_mare(X)). unde mai_mare(x) este o structură Prolog. Este evident că nu orice axiomă poate fi transformată în Prolog şi că, dintr-un anumit punct de vedere, puterea expresivă a limbajului este inferioară celei a logicii cu predicate de ordinul I. Pe de altă parte, limbajul Prolog oferă o mulţime de predicate de ordinul II, adică predicate care acceptă ca argumente alte predicate Prolog, care nu sunt permise în logica cu predicate de ordinul I. Acest lucru oferă limbajului Prolog o putere de calcul superioară
  • 24. 24 celei din logica clasică. Uneori, aceste predicate de ordinul II existente în Prolog pot fi folosite pentru a modela versiuni de programe Prolog echivalente cu o mulţime de axiome care nu au o reprezentare în clauze Horn distincte. Se propune cititorului, după parcurgerea secţiunii următoare, să revină la Exemplul 1 şi să încerce găsirea unei forme Prolog relativ echivalente (cu un efect similar) cu clauza C3. Limbajul Prolog demonstrează scopuri (teoreme) prin metoda respingerii rezolutive [Flo93]. Strategia rezolutivă utilizată este strategia de intrare liniară, strategie care nu este în general completă dar este completă pentru clauze Horn. Această strategie este deosebit de eficientă din punct de vedere al implementării şi jutifică astfel forma restricţionată a clauzelor Prolog. 4 Structura de control a limbajului Prolog În această secţiune se prezintă în detaliu structura de control specifică sistemului Prolog. Spre deosebire de limbajele de programare clasice, în care programul defineşte integral structura de control şi fluxul de prelucrări de date, în Prolog există un mecanism de control predefinit. 4.1 Semnificaţia declarativă şi procedurală a programelor Prolog Semnificaţia declarativă a unui program Prolog se referă la interpretarea strict logică a clauzelor acelui program, rezultatul programului fiind reprezentat de toate consecinţele logice ale acestuia. Semnificaţia declarativă determină dacă un scop este adevărat (poate fi satisfăcut) şi, în acest caz, pentru ce instanţe de variabile este adevărat scopul. Se reaminteşte că o instanţă a unei clauze este clauza de bază (clauza fără variabile) obţinută prin instanţierea variabilelor din clauza iniţială. În aceste condiţii semnificaţia declarativă a unui program Prolog se poate defini precum urmează: Definiţie. Un scop S este adevărat într-un program Prolog, adică poate fi satisfăcut sau derivă logic din program, dacă şi numai dacă: 1. există o clauză C a programului; 2. există o instanţă I a clauzei C astfel încât: 2.1. antetul lui I să fie identic cu cel al lui S; 2.2. toate scopurile din corpul lui I sunt adevărate, deci pot fi satisfăcute. Observaţii: • În definiţia de mai sus clauzele referă atât fapte cât şi reguli Prolog. Antetul unei clauze este antetul regulii dacă clauza este o regulă Prolog (clauză Horn mixtă) şi
  • 25. 25 este chiar faptul dacă clauza este un fapt Prolog (clauză unitară pozitivă). Corpul unui fapt este considerat vid şi un fapt este un scop care se îndeplineşte întotdeauna. • În cazul în care întrebarea pusă sistemului Prolog este o conjuncţie de scopuri, definiţia anterioară se aplică fiecărui scop din conjuncţie. Semnificaţia procedurală a unui program Prolog se referă la modul în care sistemul încearcă satisfacerea scopurilor, deci la strategia de control utilizată. Diferenţa dintre semnificaţia declarativă şi semnificaţia procedurală este aceea că cea de a doua defineşte, pe lânga relaţiile logice specificate de program, şi ordinea de satisfacere a scopurilor şi subscopurilor. În prima secţiune a acestui capitol s-a făcut o prezentare informală a modalităţii procedurale de satisfacere a scopurilor în Prolog. În continuare se rafinează această comportare. Din punct de vedere procedural, un program Prolog poate fi descris de schema bloc prezentată în figura 3. Figura 3. Comportarea procedurală a sistemului Prolog Semnificaţia procedurală a unui program Prolog poate fi descrisă de următorul algoritm în care L = {S1, S2, …, Sn} este lista de scopuri de satisfăcut, iar B este lista de instanţieri (unificări) ale variabilelor din scopuri, iniţial vidă. Această listă se va actualiza la fiecare apel. Algoritm. Strategia de control Prolog SATISFACE(L,B) 1. dacă L = {} % lista vidă atunci afişează B şi întoarce SUCCES. 2. Fie S1 ← primul scop din L şi p predicatul referit de S1. Parcurge clauzele programului, de la prima clauză sau de la ultimul marcaj fixat, asociat lui p, până ce se găseşte o clauză C al cărei antet unifică cu S1. 3. dacă nu există o astfel de clauză atunci întoarce INSUCCES. 4. Fie C de forma H :- D1,…,Dm, m ≥ 0. Plasează un marcaj în dreptul clauzei C, asociat lui p. (H conţine predicatul p). 5. Redenumeşte variabilele din C şi obtine C' astfel încât să nu existe nici o variabilă comună între C' şi L; C' este de tot forma H :- D1,…,Dm. cu redenumirile făcute. 6. L’ ← { D1,…,Dm, S2,…,Sn } % dacă C este fapt, atunci L se va reduce Execuþie sistem Prolog conjuncþii de scopuri program Prolog indicator SUCCES/INSUCCES instanþele variabilelor din scopuri
  • 26. 26 7. Fie B1 instanţierea variabilelor care rezultă din unificarea lui S1 cu H. 8. Substituie variabilele din L’ cu valorile date de B1 şi obţine: L” ← { D1’,…,Dm’, S2’,…,Sn’ }. 9. B ← B ∪ B1. 10.dacă SATISFACE(L”, B)=SUCCES atunci afişează B şi întoarce SUCCES. 11.repetă de la 1. sfârşit Observaţii: • Algoritmul de mai sus reprezintă de fapt implementarea strategiei rezolutive de intrare liniară utilizată de limbajul Prolog, pentru care se impune o ordine prestabilită de considerare a clauzelor. • Algoritmul arată funcţionarea sistemului pentru găsirea primei soluţii. • Indicatorul SUCCES/INSUCCES corespunde răspunsurilor de yes, respectiv no, date de sistemul Prolog la încercarea de satisfacere a unei liste de scopuri. Următoarele două exemple pun în evidenţă diferenţa dintre semnificaţia declarativă şi semnificaţia procedurală a programelor Prolog. Primul exemplu este un scurt program care defineşte relaţiile de părinte şi strămoş existente între membrii unei familii. Se dau patru definiţii posibile ale relaţiei de strămoş, str1, str2, str3 şi str4, toate fiind perfect corecte din punct de vedere logic, deci din punct de vedere a semnificaţiei declarative a limbajului. % parinte(IndividX, IndividY) % stramos(IndividX, IndividZ) parinte(vali, gelu). parinte(ada, gelu). parinte(ada, mia). parinte(gelu, lina). parinte(gelu, misu). parinte(misu, roco). str1(X, Z) :- parinte(X, Z). str1(X, Z) :- parinte(X, Y), str1(Y, Z). % Se schimbă ordinea regulilor: str2(X, Z) :- parinte(X, Y), str2(Y, Z). str2(X, Z) :- parinte(X, Z).
  • 27. 27
  • 28. 28 % Se schimbă ordinea scopurilor în prima variantă: str3(X, Z) :- parinte(X, Z). str3(X, Z) :- str3(X, Y), parinte(Y, Z). % Se schimbă atât ordinea regulilor, cât şi ordinea scopurilor: str4(X, Z) :- str4(X, Y), parinte(Y, Z). str4(X, Z) :- parinte(X,Z). Figura 4. Arborele genealogic definit de programul Prolog Figura 4 prezintă arborele genealogic definit de faptele Prolog anterioare. În raport cu semantica declarativă a limbajului se pot schimba, fără a modifica înţelesul logic, atât ordinea clauzelor care definesc relaţia de strămoş, cât şi ordinea scopurilor în corpul regulii de aflare a strămoşilor. Schimbând ordinea clauzelor se obţine din predicatul str1 definiţia alternativă a relaţiei de strămoş str2; schimbând ordinea scopurilor din corpul regulii în varianta iniţială se obţine definiţia predicatului str3; şi, schimbând atât ordinea clauzelor, cât şi ordinea scopurilor în regulă, se obţine predicatul str4. Comportarea programului folosind cele patru definiţii alternative, dacă se doreşte aflarea adevărului relaţiei de strămoş pentru perechile (ada, mişu) şi (mia, roco), este cea prezentată în continuare. ?- str1(ada, misu). yes Pentru acest scop, arborele de deducţie este prezentat în figura 5. vali ada gelu mia lina mişu roco
  • 29. 29 Figura 5. Arborele de deducţie a scopului str1(ada, misu) ?- str2(ada, misu). yes ?- str3(ada, misu). yes Pentru scopul str3(ada, misu), arborele de deducţie este cel prezentat în figura 6. Figura 6. Arborele de deducţie a scopului str3(ada, misu) ?- str4(ada, misu). % buclă infinita; eventual mesaj de depăşire a stivei Pentru acest scop, arborele de deducţie este cel prezentat în figura 7. str1(ada, misu) parinte(ada, misu) INSUCCES parinte(ada, Y), str1(Y, misu) parinte(ada, gelu) Y=gelu SUCCES str1(gelu, misu) parinte(gelu, misu) SUCCES str3(ada, misu) parinte(ada, misu) INSUCCES str3(ada, Y), parinte(Y, misu) parinte(ada, Y') Y=Y' SUCCES parinte(gelu, misu) SUCCES parinte(ada, gelu) Y'=gelu
  • 30. 30 Figura 7. Arborele de deducţie (infinit) a scopului str4(ada, misu) Din punctul de vedere al semnificaţiei procedurale, cele patru definiţii nu sunt echivalente. Primele două, str1 şi str2, pot da răspuns pozitiv sau negativ la orice întrebare, dar str2 este mai ineficientă decât str1. Cititorul poate încerca trasarea arborelui de deducţie al satisfacerii scopului str2(ada, misu) şi compararea acestuia cu cel corespunzător satisfacerii scopului str1(ada, misu). Definiţia str4 produce o buclă infinită datorită intrării infinite în recursivitate. Este evident o definiţie greşită din punct de vedere procedural. Definiţia relaţiei de strămoş str3 este o definiţie "capcană". După cum se poate observă din arborele de deducţie prezentat, răspunsul sistemului este afirmativ în cazul în care există o relaţie de strămoş între cele două persoane argumente ale predicatului. În cazul în care o astfel de relaţie nu există, sistemul intră într-o buclă infinită, cum ar fi, de exemplu, pentru persoanele mia şi roco. ?- str1(mia, roco). no ?- str2(mia, roco). no ?- str3(mia, roco). % buclă infinită; eventual mesaj de depăşire a stivei În acest caz, arborele de deducţie al scopului str3(mia, roco) este prezentat în figura 8. Datorită semnificaţiei procedurale a limbajului Prolog trebuie urmărit cu atenţie modul de definire a unui predicat atât din punctul de vedere al ordinii clauzelor, cât şi din punctul de vedere al ordinii scopurilor în corpul regulilor. str4(ada, misu) str4(ada, Y), parinte(Y, misu) str4(ada, Y'), parinte(Y', Y) str4(ada, Y"), parinte(Y", Y) arbore infinit ...
  • 31. 31 Figura 8. Arbore de deducţie infinit pentru un scop fals Al doilea exemplu se referă la rezolvarea în Prolog a următoarei probleme. O maimuţă se găseşte la uşa unei camere şi o banană se află agăţată de plafon în centrul camerei. Lângă fereastra camerei se află o cutie pe care maimuţa o poate folosi pentru a se urca pe ea şi a ajunge la banană. Maimuţa ştie să facă următoarele mişcări: să meargă pe sol, să se urce pe cutie, să deplaseze cutia dacă este lângă cutie, să apuce banana dacă este pe cutie şi cutia este în centrul camerei. Se cere să se scrie un program Prolog care să descrie această problema şi care să poată răspunde dacă, dintr-o configuraţie iniţială specificată prin poziţia maimuţei şi a cubului, maimuţa poate apuca banana după execuţia unei secvenţe de mişcări. Reprezentarea universului problemei în Prolog este specificată după cum urmează. Starea iniţială este: (1) Maimuţa este la uşă. (2) Maimuţa este pe sol. (3) Cutia este la fereastră. (4) Maimuţa nu are banana. şi poate fi descrisă prin structura Prolog: stare(la_usa, pe_sol, la_fereastra, nu_are_banana) Starea finală este aceea în care maimuţa are banana stare( _ , _ , _ , are_banana) Mişcările cunoscute de maimuţă, deci operatorii de tranziţie dintr-o stare în alta, sunt: (m1) Apucă banana. apucă (m2) Urcă pe cutie. urcă (m3) Mută cutia. mută(Poziţia1, Poziţia2) str3(mia, roco) parinte(mia, roco) INSUCCES str3(mia, Y), parinte(Y, roco) parinte(mia, Y) INSUCCES str3(mia, Y'), parinte(Y', Y) parinte(mia, Y') INSUCCES str3(mia, Y"), parinte(Y", Y') parinte(mia, Y") INSUCCES ... arbore infinit
  • 32. 32 (m4) Merge (maimuţa se deplasează pe sol). merge(Poziţia1, Poziţia2) şi sunt reprezentate prin structurile Prolog indicate la dreapta mişcărilor. Dintr-o anumită stare numai anumite mişcări sunt permise. De exemplu, maimuţa nu poate apuca banana decât dacă este urcată pe cutie şi cutia este în centrul camerei, adică sub banană. Mişcările permise pot fi reprezentate în Prolog prin predicatul de deplasare depl cu trei argumente: depl(Stare1, Mişcare, Stare2) care transformă problema din starea Stare1 în starea Stare2 prin efectuarea mişcării legale Mişcare în starea Stare1. Se observă că reprezentarea aleasă este o reprezentare a problemei folosind spaţiul stărilor [Flo93]. Soluţia problemei este completată prin adăugarea predicatului poatelua(Stare) care va reuşi dacă maimuţa poate ajunge din starea iniţială Stare într-o stare finală în care poate lua banana, stare finală descrisă de: poate_lua(stare( _ , _ , _ , are_banana)). Programul Prolog complet este prezentat în continuare. % Structura stare: % stare(poz_o_maimuta, poz_v_maimuta, poz_cub, are_nu_are_banana) % Mişcări admise: apucă, urca, muta(Pozitia1, Pozitia2), merge(Pozitia1, Pozitia2), % reprezentate tot prin structuri. % Predicate: % depl(Stare1, Miscare, Stare2) % poate_lua(Stare) depl(stare(la_centru, pe_cutie, la_centru, nu_are_banana), apuca, stare(la_centru, pe_cutie, la_centru, are_banana)). depl(stare(P, pe_sol, P, H), urca, stare(P, pe_cutie, P, H)). depl(stare(P1, pe_sol, P1, H), muta(P1, P2), stare(P2, pe_sol, P2, H)). depl(stare(P1, pe_sol, B, H), merge(P1, P2), stare(P2, pe_sol, B, H)). poate_lua(stare( _ , _ , _ , are_banana)). poate_lua(Stare1) :- depl(Stare1, Miscare, Stare2), poate_lua(Stare2). La întrebarea ?- poate_lua(stare(la_usa, pe_sol, la_fereastra, nu_are_banana)). yes sistemul răspunde afirmativ: maimuţa este fericită şi mănâncă banana. Stare1 Stare2 Mişcare
  • 33. 33 O analiză atentă a programului conduce la concluzia că programul dă soluţii numai pentru anumite situaţii. În primul rând, strategia de control a mişcărilor maimuţei este impusă de ordinea clauzelor care definesc predicatul depl. Astfel, maimuţa preferă întâi să apuce, apoi să urce, apoi să mute cubul şi numai la urmă să mearga prin cameră. Dacă clauza corespunzătoare mişcării merge(Pozitia1, Pozitia2) ar fi fost pusă ca prima clauză în definiţia predicatului de deplasare, maimuţa ar fi mers la infinit prin cameră fără să mai ajungă sa mute cubul sau să apuce banana. Chiar pentru ordinea dată a clauzelor, dacă se pune întrebarea ?- poate_lua(stare(X, pe_sol, la_fereastra, nu_are_banana)). deci dacă interesează din ce poziţii maimuţa poate lua banana, rezolvarea dată nu este total satisfăcătoare deoarece programul are o infinitate de soluţii. La cererea repetată a unei soluţii, se va afişa întotdeauna valoarea: X = la_fereastra Considerând din nou modelul spaţiului stărilor, se observă că în acest caz este vorba de un spaţiu de căutare de tip graf în care o aceeaşi stare poate fi descoperită şi redescoperită la infinit prin parcurgerea unui ciclu de tranziţii de stări în acest graf. Aşa cum este arătat în [Flo93], astfel de cazuri trebuie tratate prin introducerea unor liste de stări parcurse (listele FRONTIERA şi TERITORIU) care să împiedice parcurgerea repetată a unor stări deja parcurse. Pericolul de apariţie a unor cicluri infinite datorită parcurgerii unui spaţiu de căutare graf nu este specific limbajului Prolog şi poate să apară în orice implementare, în aceste cazuri. Ceea ce este neobişnuit în raport cu alte limbaje este faptul că semnificaţia declarativă a programului este corectă, indiferent de ordonarea clauzelor, în timp ce programul este procedural incorect, având comportări diferite în funcţie de această ordonare. Rezolvarea problemei ar mai trebui completată cu afişarea stărilor şi a mişcărilor executate de maimuţă pentru a ajunge în starea finală în care ea poate apuca banana. Modalităţi de eliminare a ciclurilor infinite de tipul celor ce apar în această problemă vor fi discutate in partea a doua a lucrării. 4.2 Controlul procesului de backtracking: cut şi fail Sistemul Prolog intră automat într-un proces de backtraking dacă acest lucru este necesar pentru satisfacerea unui scop. Acest comportament poate fi în unele situaţii deosebit de util, dar poate deveni foarte ineficient în alte situaţii. Se consideră următorul exemplu de program în care se definesc valorile unei funcţii: f(X, 0) :- X < 3. % 1 f(X, 2) :- 3 =< X, X < 6. % 2 f(X, 4) :- 6 =< X. % 3 La întrebarea:
  • 34. 34 ?- f(1, Y). Y = 0 sistemul răspunde indicând că valoarea funcţiei pentru X=1 este Y=0. Dacă se pune întrebarea formată din conjuncţia de scopuri: ?- f(1, Y), 2 < Y. no sistemul semnalează eşec. Arborii de deducţie corespunzători acestor răspunsuri sunt prezentaţi în figura 9. Figura 9. Arborii de deducţie a scopurilor f(1,Y) şi f(1,Y),2 < Y Se observă că se încearcă resatisfacerea primului scop cu regulile 2 şi 3, deşi acest lucru este evident inutil datorită semanticii acestor reguli. Cel care a scris aceste reguli poate cu uşurinţă constata că, dacă o valoare mai mică decât 3 pentru X duce la eşecul scopului S din conjuncţia de scopuri f(X, Y), S, este inutil să se încerce resatisfacerea scopului f, deoarece aceasta nu este posibilă [Bra88]. Se poate împiedica încercarea de resatisfacere a scopului f(X, Y) prin introducerea predicatului cut. Predicatul cut, notat cu atomul special !, este un predicat standard, fără argumente, care se îndeplineşte (este adevărat) întotdeauna şi nu poate fi resatisfăcut. Predicatul cut are următoarele efecte laterale: • La întâlnirea predicatului cut toate selecţiile făcute între scopul antet de regulă şi cut sunt "îngheţate", deci marcajele de satisfacere a scopurilor sunt eliminate, ceea ce duce la eliminarea oricăror altor soluţii alternative pe aceasta porţiune. O încercare de resatisfacere a unui scop între scopul antet de regula şi scopul curent va eşua. • Dacă clauza în care s-a introdus predicatul cut reuşeşte, toate clauzele cu acelasi antet cu aceasta, care urmează clauzei în care a apărut cut vor fi ignorate. Ele nu se mai folosesc în încercarea de resatisfacere a scopului din antetul clauzei care conţine cut. f(1,Y) f(1,0) X=1 Y=0 1<3 SUCCES f(1,Y), 2<Y f(1,0) X=1 Y=0 2<0 INSUCCES 1=<3 f(1,2) X=1 Y=2 f(1,4) X=1 Y=4 INSUCCES 3=<1 6=<1 INSUCCES
  • 35. 35 • Pe scurt, comportarea predicatului cut este următoarea: (C1) H :- D1, D2, …, Dm, !, Dm+1, …, Dn. (C2) H :- A1, …, Ap. (C3) H. Dacă D1, D2, …, Dm sunt satisfăcute, ele nu mai pot fi resatisfăcute datorită lui cut. Dacă D1, …, Dm sunt satisfăcute, C2 şi C3 nu vor mai fi utilizate pentru resatisfacerea lui H. Resatisfacerea lui H se poate face numai prin resatisfacerea unuia din scopurile Dm+1, …, Dn, dacă acestea au mai multe soluţii. Utilizând predicatul cut, definiţia funcţiei f(X, Y) poate fi rescrisă mult mai eficient astfel: f(X, 0) :- X < 3, !. f(X, 2) :- 3 =< X, X < 6, !. f(X, 4) :- 6 =< X. Predicatul cut poate fi util în cazul în care se doreşte eliminarea unor paşi din deducţie care nu conţin soluţii sau eliminarea unor căi de căutare care nu conţin soluţii. El permite exprimarea în Prolog a unor structuri de control de tipul: dacă condiţie atunci acţiune1 altfel acţiune2 astfel: daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1. daca_atunci_altfel(Cond, Act1, Act2) :- Act2. Se observă însă că există două contexte diferite în care se poate utiliza predicatul cut: într-un context predicatul cut se introduce numai pentru creşterea eficienţei programului, caz în care el se numeşte cut verde; în alt context utilizarea lui cut modifică semnificaţia procedurală a programului, caz în care el se numeşte cut roşu. Exemplul de definire a funcţiei f(X, Y) cu ajutorul lui cut este un exemplu de cut verde. Adăugarea lui cut nu face decât să crească eficienţa programului, dar semnificaţia procedurală este aceeaşi, indiferent de ordinea în care se scriu cele trei clauze. Utilizarea predicatului cut în definirea predicatului asociat structurii de control daca_atunci_altfel introduce un cut roşu deoarece efectul programului este total diferit dacă se schimbă ordinea clauzelor. Introducerea unui cut roşu modifică corespondenţa dintre semnificaţia declarativă şi semnificaţia procedurală a programelor Prolog. Se consideră exemplul de definire a predicatului de aflare a minimului dintre două numere, în următoarele două variante:
  • 36. 36 min1(X, Y, X) :- X =< Y, !. % cut verde min1(X, Y, Y) :- X > Y. min2(X, Y, X) :- X =< Y, !. % cut roşu min2(X, Y, Y). În definiţia predicatului min1 se utilizează un cut verde; el este pus pentru creşterea eficienţei programului, dar ordinea clauzelor de definire a lui min1 poate fi schimbată fără nici un efect asupra rezultatului programului. În cazul predicatului min2 se utilizează un cut roşu, asemănător structurii daca_atunci_altfel. Dacă se schimbă ordinea clauzelor de definire a predicatului min2: min2(X, Y, Y). min2(X, Y, X) :- X =< Y, !. rezultatul programului va fi evident incorect pentru valori X < Y. În anumite cazuri efectul introducerii predicatului cut poate fi mai subtil. Se consideră din nou definiţia predicatului membru: membru(X, [X | _]). membru(X, [ _ |Y]) :- membru(X, Y). şi o definiţie alternativă membru1(X, [X| _]) :- !. membru1(X, [ _ |Y]) :- membru1(X, Y). Introducerea predicatului cut în definiţia primei clauze a predicatului membru1 este justificată datorită creşterii eficienţei programului. Odată ce s-a descoperit că un element este membru al unei liste, este inutilă încercarea de resatisfacere a scopului. Cu toate acestea predicatul cut de mai sus nu este un cut verde, deoarece el schimbă comportarea programului în cazul în care se pun întrebări în care X este variabila neinstanţiată în predicatele membru şi membru1. ?- membru(X, [a, b, c]). X = a; X = b; X = c; no trei soluţii pentru membru ?- membru1(X, [a, b, c]). X = a; no
  • 37. 37 o soluţie pentru membru1. Efectul introducerii predicatului cut asupra semnificaţiei declarative a programelor Prolog poate fi rezumat astfel: p :- a, b. % Semnificaţia declarativă este: p :- c. % (a ∧ b) ∨ c % indiferent de ordinea clauzelor (în general). p :- a, !, b. % Semnificaţia declarativă este: p :- c. % (a ∧ b) ∨ (~ a ∧ c). p :- c. % Semnificaţia declarativă este: p :- a, !, b. % c ∨ (a ∧ b). Oportunitatea utilizării unui cut roşu sau a unui cut verde este, dintr-un anumit punct de vedere, similară cu cea a utilizării sau nu a salturilor în limbajele de programare clasică. Dacă s-ar rescrie predicatul daca_atunci_altfel folosind un cut verde, definiţia lui ar fi: daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1. daca_atunci_altfel(Cond, Act1, Act2) :- not (Cond), !, Act2. unde predicatul not(Cond) este satisfăcut dacă scopul Cond nu este satisfăcut. O astfel de definiţie implică evaluarea lui Cond de două ori şi, dacă Cond se defineşte ca o conjuncţie de scopuri, posibil sofisticate, atunci ineficienţa utilizării unui cut verde în acest caz este evidentă. De la caz la caz, programatorul în Prolog trebuie să aleagă între claritate, deci păstrarea corespondenţei semnificaţiei declarative cu cea procedurală, şi eficienţă. În multe situaţii interesează atât condiţiile de satisfacere a scopurilor, cât şi condiţiile de nesatisfacere a acestora, deci de eşec. Fie următoarele fapte Prolog: bun(gelu). bun(vlad). bun(mihai). Pe baza acestor fapte se poate obţine un răspuns afirmativ despre bunătatea lui Gelu, Vlad şi Mihai şi un răspuns negativ dacă se întreabă despre bunătatea oricărei alte persoane. Din acest exemplu se poate vedea că limbajul Prolog foloseşte un model de raţionament minimal numit ipoteza lumii închise. Conform acestui model, tot ceea ce nu este ştiut de program, deci afirmat explicit ca adevărat în program, este considerat fals. Limbajul Prolog permite exprimarea directă a eşecului unui scop cu ajutorul predicatului fail. Predicatul fail este un predicat standard, fără argumente, care eşueaza întotdeauna. Datorită acestui lucru introducerea unui predicat fail într-o conjuncţie de scopuri, de obicei la sfârşit, căci după fail tot nu se mai poate satisface nici un scop, determină intrarea în procesul de backtracking.
  • 38. 38 Dacă fail se întâlneste după predicatul cut, nu se mai face backtracking. Enunţul "Un individ este rău dacă nu este bun." se poate exprima astfel: bun(gelu). bun(vlad). bun(mihai). rau(X) :- bun(X), !, fail. rau(X). ?- rau(gelu). no ?- rau(mihai). no ?- rau(petru). yes Dacă predicatul fail este folosit pentru a determina eşecul, cum este cazul exemplului de mai sus, este de obicei precedat de predicatul cut, deoarece procesul de backtracking pe scopurile care îl preced este inutil, scopul eşuând oricum datorită lui fail. Există cazuri în care predicatul fail este introdus intenţionat pentru a genera procesul de backtracking pe scopurile care îl preced, proces interesant nu din punctul de vedere al posibilităţii de resatisfacere a scopului ce conţine fail, ci din punctul de vedere al efectului lateral al acestuia. rosu(mar). rosu(cub). rosu(soare). afisare(X) :- rosu(X), write(X), fail. afisare( _ ). Scopul afisare(X) va afişa toate obiectele roşii cunoscute de programul Prolog datorită procesului de backtracking generat de fail; în acest fel s-a relizat prin fail o iteraţie peste faptele rosu( ). Clauza afisare( _ ) este adăugată pentru ca răspunsul final la satisfacerea scopului să fie afirmativ. În acest caz, introducerea unui cut înainte de fail ar fi determinat numai afişarea primului obiect roşu din program. Revenind la combinaţia !,fail, se consideră în continuare implementarea în Prolog a afirmaţiei: "Mihai iubeşte toate sporturile cu excepţia boxului". Această afirmaţie poate fi exprimată în pseudocod sub forma: dacă X este sport şi X este box atunci Mihai iubeşte X este fals altfeldacă X este sport
  • 39. 39 atunci Mihai iubeşte X este adevărat şi tradusă în Prolog astfel: iubeste(mihai, X) :- sport(X), box(X), !, fail. iubeste(mihai, X) :- sport(X). Predicatul cut utilizat aici este un cut roşu. Combinaţia !, fail este deseori utilizată în Prolog şi are rolul de negaţie. Se mai spune că limbajul Prolog modelează negaţia ca eşec al satisfacerii unui scop (negaţia ca insucces), aceasta fiind de fapt o particularizare a ipotezei lumii închise. Combinaţia !, fail este echivalentă cu un predicat standard existent în Prolog, predicatul not. Predicatul not admite ca argument un predicat Prolog şi reuşeşte dacă predicatul argument eşuează. Utilizând acest predicat, ultimul exemplu dat se poate exprima în Prolog astfel: iubeste(mihai, X) :- sport(X), not(box(X)). iubeste(mihai, X) :- sport(X). Un alt predicat standard este predicatul call, care admite ca argument un predicat Prolog şi are ca efect încercarea de satisfacere a predicatului argument. Predicatul call reuşeşte dacă predicatul argument reuşeşte şi eşuează în caz contrar. Utilizând acest predicat, se poate explicita efectul general al predicatului standard not în următorul mod: not(P) :- call(P), !, fail. not(P). Atât predicatul not cât şi predicatul call sunt predicate de ordinul II în Prolog deoarece admit ca argumente alte predicate. Secţiunea următoare va prezenta mai multe astfel de predicate. 4.3 Predicate predefinite ale limbajului Prolog Predicatele prezentate până acum, cu excepţia predicatului call, sunt predicate bazate pe logica cu predicate de ordinul I, modelul Prolog cu semnificaţia declarativă a programelor urmărind îndeaproape modelul logic. În continuare se prezintă o serie de predicate Prolog care nu mai pot fi asimilate cu logica cu predicate de ordinul I, respectiv: predicate ce decid asupra caracterului unor argumente, predicate de control a fluxului de execuţie a programului şi predicate de ordinul II, numite uneori şi metapredicate. Metapredicatele acceptă ca argumente alte predicate Prolog, fapte sau reguli, şi sunt interesante în special prin efectul lateral pe care îl produc. Toate tipurile de predicate menţionate reprezintă extinderea modelului programării logice cu tehnici de programare care cresc puterea de calcul a limbajului. Majoritatea predicatelor prezentate în continuare sunt standard şi se găsesc predefinite în orice implementare, o parte sunt specifice mediului ARITY Prolog, acestea fiind indicate ca atare. O implementare particulară poate conţine şi alte predicate predefinite suplimentare celor prezentate aici.
  • 40. 40 (a) Predicate de clasificare a termenilor • var(X) Predicatul var(X) reuşeşte dacă X este o variabilă neinstanţiată şi eşuează în cazul în care X este o variabila instanţiată sau o constantă. Exemple: ?- var(5). no ?- var(mihai). no ?- var(X). yes • novar(X) Predicatul novar(X) este adevărat dacă X nu este o variabilă neinstanţiată. Acest predicat este opusul prdicatului var(X) şi s-ar putea defini astfel: novar(X) :- var(X), !, fail. novar( _ ). • atom(X) Predicatul atom(X) reuşeşte dacă X este un atom Prolog şi eşuează în caz contrar. Exemple: ?- atom(coco). yes ?- atom(100). no ?- atom(carte(prolog, clocksin)). no • integer(X) Predicatul integer(X) reuşeşte dacă X este un număr întreg. Se consideră urmatorul exemplu de program Prolog care transformă o expresie reprezentată printr-o sumă de variabile şi constante într-o expresie care conţine suma variabilelor plus o constantă care reprezintă suma tuturor constantelor din expresie [CM84]. Predicatul simplif are două argumente: primul argument este forma iniţială a expresiei, argument instanţiat, iar cel de al doilea reprezintă expresia simplificată, sintetizată de program. % simplif(Expresie_iniţială, Expresie_simplificată)
  • 41. 41 % Predicat ajutator simpl % simpl(Ex_iniţială, Suma_acumulată, Ex_simplificată). simplif(Expr, Expr1) :- simpl(Expr, 0, Expr1). simpl(Expr + A, N, Expr1) :- integer(A), !, N1 is N + A, simpl(Expr, N1, Expr1). simpl(Expr + A, N, Expr1 + A) :- atom(A), !, simpl(Expr, N, Expr1). simpl(Rest, N, N1) :- integer(Rest), !, N1 is Rest + N. simpl(Rest, 0, Rest) :- !. simpl(Rest, N, N + Rest). La întrebarea ?- simplif(x + 40 + y + 1 + 55 + x + 2, E). programul răspunde E = 98 + x + y + x În programul de mai sus definirea predicatului simplif s-a făcut pe baza unui predicat ajutător simpl care conţine un argument suplimentar. Acest argument, instanţiat la 0 în cazul primului apel, este folosit ca o variabilă de acumulare pentru calculul sumei constantelor întregi din expresie. • atomic(X) Pe baza predicatelor atom(X) şi integer(X) se poate defini predicatul atomic(X) care este adevărat dacă X este fie întreg, fie atom Prolog: atomic(X) :- atom(X). atomic(X) :- integer(X). De multe ori predicatul atomic(X) este predefinit în sistem.
  • 42. 42 (b) Predicate de control Predicatul true reuşeşte întotdeauna, iar fail eşuează întotdeauna. Predicatul repeat simulează structurile de control repetitive din limbajele clasice de programare. El are o infinitate de soluţii, putându-se resatisface de oricâte ori este nevoie, fără a umple stiva. Definiţia lui ar putea fi: repeat. repeat :- repeat. Acest predicat este predefinit in mediul ARITY. În ARITY Prolog există şi alte predicate predefinite care simulează structurile de control din programarea procedurală: ifthen(+Conditie, +Scop), pentru dacă-atunci; ifthenelse(+Conditie, +Scop-then, +Scop- else), pentru dacă-atunci-altfel; şi case([Conditie1 -> Scop1, … Conditien -> Scopn | Scop- default), sau case([Conditie1 -> Scop1, … Conditien -> Scopn), pentru case. O predicat echivalent ciclului for, care nu este prefedinit în ARITY, se poate implementa astfel: % for(Variabla, ValoareInitiala, ValoareFinala, Pas) for(I, I, I, 0). for( _, _ , _ , 0) :- !, fail. for(I, I, F, S) :- S > 0, (I > F, !, fail ; true) ; S < 0, (I < F, !, fail ; true). for(V, I, F, S) :- I1 is I + S, for(V, I1, F, S). Exemple de apeluri: a :- for(X, 10, -10, -3.5), write(X), tab(2), fail ; true. b :- for(X, -10, 10, 3.5), write(X), tab(2), fail ; true. c :- for(X, 10, 10, 0), write(X), tab(2), fail ; true. ?- a. 10 6.5 3.0 -0.5 -4.0 -7.5 yes ?- b. -10 -6.5 -3.0 0.5 4.0 7.5 yes ?- c. 10 yes Predicatul for eşuează pentru apeluri incorecte (cicluri infinite) de genul: for(X, -10, 10, 0). for(X, 10, -10, 2). for(X, -10, 10, -2).
  • 43. 43 (c) Predicate de tratare a clauzelor drept termeni Predicatele din această categorie permit: construirea unei structuri care reprezintă o clauză în baza de cunoştinte, identificarea clauzelor din program, adăugarea sau eliminarea unor clauze, reprezentate printr-o structură, în baza de cunoştinţe a sistemului. • clause(Antet, Corp) Satisfacerea scopului clause(Antet, Corp) necesită unificarea argumentelor Antet şi Corp cu antetul şi corpul unei clause existente în baza de cunoştinţe Prolog. La apelul acestui predicat, Antet trebuie să fie instanţiat astfel încât predicatul principal al clauzei să fie cunoscut. Dacă nu există clauze pentru acest predicat, scopul eşuează. Dacă există mai multe clauze care definesc predicatul Antet, scopul clause(Antet, Corp) va avea mai multe soluţii, fiecare soluţie corespunzând unei clauze de definire a predicatului Antet. Dacă o clauză este fapt, Corp se instanţiază la constanta true. Exemplu. Considerând definiţia predicatului de concatenare a două liste ca fiind deja introdusă în baza de cunoştinţe Prolog: % conc(Lista1, Lista2, ListaRez) conc([], L, L). conc([Prim|Rest1], L2, [Prim|Rest3]) :- conc(Rest1, L2, Rest3). se obţin următoarele rezultate: ?- clause(conc(A, B, C), Corp). A = [ ], B = _004C, C = _004C, Corp = true; A = [_00F0|_00F4], B = _004C, C = [_00F0|_00FC], Corp = conc (_00F4, _004C, _00FC); no Utilizând predicatul clause se poate defini un predicat listc de afişare a clauzelor care definesc un predicat: % listc(Antet) - afişează toată clauzele din baza de cunoştinţe % care definesc scopul Antet listc(Antet) :- clause(Antet, Corp), afis(Antet, Corp), write('.'), nl, fail. listc( _ ). afis(Antet, true) :- !, write(Antet). %1 afis(Antet, Corp) :- write(Antet), write(':'), write('-'), write(Corp). Efectul acestui program, coinsiderând definiţia anterioară a scopului conc, este următorul:
  • 44. 44 ?- listc(conc(_, _, _)). conc([], _0034, _0034). conc([_0108|_010C], _0034, [_0108|_0114]:-conc(_010C, _0034, _0114). În definirea primei clauze a predicatului afis utilizarea predicatului cut este esenţială deoarece afişarea clauzelor este bazată pe procesul de backtracking generat intenţionat de introducerea predicatului fail în prima clauză a predicatului listc. Regula %1 din predicatul afis este necesară deoarece faptele sunt reguli având corpul format din scopul true. Această reprezentare ne arată că faptele şi regulile au aceeaşi reprezentare în Prolog (toate reprezintă clauze). Exemplu. Pentru următorul predicat: p(1). p(X) :- X > 2, X < 5. p(X). predicatul clause dă următoarele răspunsuri: ?- clause(p(X), Corp). X = 1, Corp = true ; X = _0038, Corp = _0038 > 2 , _0038 < 5 ; X = _0038, Corp = true. • asserta(Clauza), assertz(Clauza), assert(Clauza), Predicatul asserta(Clauza) reuşeşte întotdeauna o singură dată şi are ca efect lateral adăugarea clauzei Clauza la începutul bazei de cunoştinţe Prolog. Predicatele assertz(Clauza) şi assert(Clauza) au o comportare similară, cu excepţia faptului că argumentul Clauza este adăugat la sfârşitul bazei de cunoştinţe. Argumentul Clauza trebuie să fie instanţiat la o valoare ce reprezintă o clauză Prolog. Acţiunea de adăugare a unei clauze prin asserta, assert sau assertz nu este eliminată de procesul de backtracking. Clauza adăugată va fi eliminată numai în urma unei cereri explicite prin intermediul predicatului retract. • retract(Clauza) Predicatul retract(Clauza) are ca efect eliminarea din baza de cunoştinţe a unei clauze care unifică cu argumentul Clauza. Predicatul reuşeşte dacă o astfel de clauza exista; la fiecare resatisfacere se elimină din baza de cunoştinţe o nouă clauză care unifică cu argumentul. Clauza trebuie sa fie argument instanţiat la apel. Odată ce o clauză este eliminată din baza de cunoştinţe prin retract, clauza nu va mai fi readaugată dacă se face backtracking pe retract. Exemplu.
  • 45. 45 adaug :- asserta(prezent(nero)), asserta(prezent(cezar)). scot :-retract(prezent(nero)). actiune1 :- adaug, listc(prezent(X)). actiune2 :- scot, listc(prezent(X)). ?- actiune1. prezent(nero). prezent(cezar). ?- actiune2. prezent(cezar). Utilizând predicatul retract, se poate defini un predicat care are rolul de a elimina din baza de cunoştinţe toate clauzele care definesc un predicat. Acest predicat, numit retractall(Antet), este predefinit în anumite implementări Prolog. Definiţia lui pe baza predicatului retract este: % retractall(Antet) - elimină toate clauzele care definesc scopul Antet retractall(Antet) :- retract(Antet), fail. retractall(Antet) :- retract( (Antet :- Corp) ), fail. retractall( _ ). Observaţie: În ARITY Prolog, atunci când se adaugă/elimină o regulă în/din baza de date în mod dinamic cu assert/retract regula trebuie pusă între paranteze rotunde sau dată ca o structură: assert( (p(X) :- X=1 ; X=2) ). retract( ':-'(p(X), ';'(X=1, X=2))). Predicatele asserta, assertz şi retract pot fi utile în diverse situaţii datorită efectelor lor laterale. Ele pot simula, de exemplu, un fel de mecanism de variabile globale în Prolog. Se consideră exemplul de implementare a unui generator de numere aleatoare în Prolog. Se defineşte predicatul aleator(R, N) care generează un număr aleator N în intervalul [1..R] pornind de la o sămânţă fixată şi folosind metoda congruenţei liniare. De fiecare dată când trebuie generat un număr aleator, valoarea acestuia este determinată folosind sămânţa existentă şi o nouă sămânţă este calculată şi memorată ca fapt Prolog până la noul apel. Vechea sămânţă este eliminată din baza de cunoştinţe . % aleator(R, N) - instanţiază N la un număr aleator între 1 şi R samanta(11). aleator(R, N) :- samanta(S), modul(S, R, N1), N is N1 + 1, retract(samanta(S)),
  • 46. 46 N2 is (125 * S +1), modul(N2, 4096, SamantaNoua), asserta(samanta(SamantaNoua)). modul(X, Y, X) :- X < Y. modul(X,Y,Z) :- % Predicatul modulo este predefinit X >= Y, % în anumite implementări X1 is X - Y, modul(X1, Y, Z). La apelul predicatului se obţin următoarele rezultate: ?- aleator(100, N). N = 14 ?- aleator(100, N). N = 27 ?- aleator(100, N). N = 48 Dacă se doreşte afişarea continuă a numerelor aleatoare, o primă variantă este aceea de a crea un ciclu infinit de afişare a acestor numere, efect realizat de predicatul nr_aleat(R) care afişează numere aleatoare în intervalul [1..R]. % nr_aleat(R) - afişează la infinit numere aleatoare între 1 şi R nr_aleat(R) :- repeat, aleator(R, X), write(X), nl, fail. Predicatul fail introdus în definiţia predicatului nr_aleat(R) determină intrarea în procesul de backtracking şi datorită predicatului repeat, cele două scopuri aleator(R, X) şi write(X) se vor satisface la infinit. Trebuie observat faptul că numai scopul repeat se resatisface; scopurile aleator şi write sunt la prima satisfacere de fiecare dată. În cazul în care se doreşte construirea unui ciclu care se va termina dacă o anumită condiţie este îndeplinită, se poate reformula predicatul de generare a numerelor aleatoare astfel: nr_aleat1(R) :- repeat, aleator(R, X), write(X), nl, write('Continuati? [d/n]'), read('n'). Apelând predicatul se va obţine următoarea secvenţă de numere aleatoare: ?- nr_aleat1(10). 4 Continuati? d. 7 Continuati? d.
  • 47. 47 8 Continuati? n. yes • functor(Term, Funct, N) Predicatul reuşeşte dacă cele trei argumente unifică astfel: Term este o structură cu functor Funct şi aritate N. Predicatul poate fi folosit în două moduri: (1) Term este argument instanţiat şi predicatul reuşeşte dacă Term este atom sau structură şi eşuează în caz contrar. În caz de succes, Funct şi N sunt instanţiate la functorul, respectiv aritatea lui Term. Un atom este considerat ca fiind o structură de aritate 0. (2) Term este argument neinstanţiat iar Funct şi N sunt instanţiate, specificând functorul şi numărul de argumente. În acest caz scopul reuşeşte întotdeauna şi Term va fi instanţiat la o structură cu functorul şi numărul de argumente specificate. Argumentele structurii construite în acest mod sunt neinstanţiate. Exemple: ?- functor(auto(honda, culoare(roşie)), Funct, N). Funct = auto, N = 2 ?- functor(a + b, F, N). F = +, N = 2 ?- functor(albastru, F, N). F = albastru, N = 0 ?- functor [a, b, c], !, 3). no ?- functor(Term, pasi_plan, 3). Term = pasi_plan(_0, _0, _0) • arg (N, Term, Arg) Predicatul arg (N, Term, Arg) are ca efect obţinerea celui de al N-lea argument al unei structuri Term, acest argument devenind instanţierea lui Arg. N şi Term trebuie să fie argumente instanţiate. Arg poate fi instanţiat, caz în care predicatul reuşeşte dacă Arg este argumentul N din structura Term, sau Arg poate fi neinstanţiat, caz în care se va instanţia la cel de al N-lea argument al lui Term. ?- arg(2, poseda(mihai, frumoasa(portocala)), Arg). Arg = frumoasa(portocala). ?- arg (2,[a, b, c], X). X = [b, c] ?- arg(1, a + (b + c), b).
  • 48. 48 no • Scop = .. [Functor | ListArg] Predicatul =.., numit univ, este un predicat folosit ca operator infixat. El poate fi folosit în trei moduri: (1) Scop este instanţiat. În acest caz predicatul reuşeşte, dacă Scop este o structură, iar Functor şi ListArg se instanţiază la functorul, respectiv lista argumentelor acelei structuri. (2) Scop este neinstanţiat, iar Functor şi ListArg sunt instanţiate la un functor, respectiv la o listă de argumente a unei structuri. Dacă valorile lui Functor şi ListArg sunt corecte, predicatul reuşeşte şi Scop va fi instanţiat la structura creată cu functorul şi lista de argumente date. (3) Toţi trei parametrii sunt instanţiati şi predicatul reuşeşte dacă Functor şi ListArg sunt functorul, respectiv lista argumentelor structurii Scop. Acest predicat are o importanţă deosebită în Prolog deoarece el permite sinteza dinamică a scopurilor Prolog, deci construirea de clauze pe parcursul execuţiei programului. Lucrul acesta este posibil deoarece clauzele Prolog au tot forma unor structuri formate din functor şi argument, sintaxa codului fiind aceeaşi cu sintaxa datelor în Prolog. Exemple: ?- sir(a, b, c) =.. X. X = [sir, a, b, c] ?- (f(a) + g(b)) =.. [+, f(X), Y]. X = a, Y = g(b) ?- a + b + c =.. L. % a + b + c = '+'(a, '+'(b, c)) L=[+, a+b, c] ?- a * b + c =.. L. % a * b + c = '+'('*'(a, b), c) L = [+, a * b, c] ?- a + b * c =.. L. % a + b * c = '+'(a, '*'(b, c)) L = [+, a, b * c] ?- Scop =.. [parinte, mihai, gina]. Scop = parinte(mihai, gina) ?- Scop =.. [membru, a, [a, b, c]]. Scop = membru(a, [a, b, c]) ?- conc([1, 2], [3], [1, 2, 3]) =.. [Functor | ListArg]
  • 49. 49 Functor = conc, ListArg = [[1, 2], [3], [1, 2, 3]] ?- S =.. [f, X, a,Y]. S = f( _004C, a, _0060 ), X = _004C, Y = _0060 ?- f =.. L. L = [f] Folosind univ putem verifica faptul că listele se reprezintă prin structuri cu punct de forma: '.'(PrimulElement, RestulListei). Lista [1, 2] este totuna cu '.'(1, [2]) sau cu '.'(1, '.'(2, []). Exemplu. ?- [1,2] =.. L. L = [. , 1, [2]]. • listing, listing(+Nume), listing(+Nume/Aritate), listing(+[Nume/Aritate…]) Predicatul listing tipăreşte din baza de cunoştinţe toate clauzele, toate clauzele unui predicat sau toate clauzele unor predicate dintr-o listă. De exemplu, pentru predicatele: p. p(1). p(X) :- X > 2, X < 5. p(X). p(1,2). p(X,Y) :- X < Y. se pot face următoarele interogări (redenumirile variabilelor sunt făcute automat de sistem): ?- listing(p/1). p(1). p(A) :- A > 2, A < 5. p(A). yes ?- listing([p/0, p/2]). p. p(1,2). p(A, B) :- A < B. yes (d) Predicate pentru execuţia dinamică a scopurilor
  • 50. 50 • call(Scop) Se presupune că argumentul Scop este instanţiat la un scop Prolog. Predicatul call(Scop) reuşeşte dacă Scop reuşeşte şi eşuează în caz contrar. La prima vedere acest predicat pare redundant deoarece execuţia scopului Scop se poate face direct în Prolog. Dacă se doreste execuţia dinamică a scopurilor care au fost sintetizate pe parcursul execuţiei unui program Prolog, atunci predicatul call este necesar. În plus, dacă nu se cunoaşte scopul de executat, de exemplu se doreşte execuţia a diverse scopuri cu argumente diferite sau cu aceleaşi argumente, se poate folosi predicatul call cu argument o variabilă care se va instanţia în funcţie de întrebarea utilizatorului sau de context. Execuţia dinamică a scopurilor poate fi exprimată prin următoarea secvenţă de scopuri: ... obţine(Functor), calculează(ArgList), Scop =.. [Functor | ArgList], call(Scop), ... Se prezintă în continuare diverse variante de predicate Prolog care aplică un predicat primit ca argument fiecărui element al unei liste sau al mai multor liste, rezultatele fiecărei aplicări fiind cumulate într-o listă rezultat. Acest tip de predicate reprezintă o categorie de prelucrări ale listelor, frecvent întâlnite şi necesare. Se defineşte întâi predicatul mapcar(Pred, ListaArg, ListaRez) care primeşte două argumente instanţiate Pred şi ListaArg şi calculează ListaRez. Pred este un predicat Prolog care are la rândul lui două argumente: primul, instanţiat, este folosit în prelucrare pentru obţinerea celui de al doilea şi este obţinut ca element curent din ListaArg; cel de al doilea, neinstanţiat, este calculat de Pred şi este depus ca element curent în ListaRez. În programul care urmează se vor folosi ca predicate Pred următoarele: • prim(Lista, El) care obţine primul element dintr-o listă, • neg(Element, -Element) care schimbă semnul unui element şi • adaug(Nume, NumeFrumos) care adaugă în faţa unui nume apelativul de politeţe dna dacă persoana este femeie şi dl dacă persoana este bărbat. Se observă că argumentele lui Pred din definiţia lui mapcar pot fi atât elemente atomice (atomi sau numere) cât şi liste. % Se definesc predicatele de prelucrare a listelor: mapcar, mapcar2 şi mapcarnl. prim([], []). prim([X | _ ], X). neg(X, -X). adaug(Nume, NumeFrumos) :- write(Nume), write(' este femeie sau barbat (f/b)? '), read(Sex), (Sex = b, NumeFrumos = [dl, Nume] ;
  • 51. 51 Sex = f, NumeFrumos = [dna, Nume]). % mapcar(Pred, ListaArg, ListaRez) - evaluează predicatul % Pred pentru fiecare element din lista ListaArg şi % construieşte rezultatul în ListaRez. % Predicatul Pred are două argumente, atomice sau liste. mapcar( _ , [], []). mapcar(P, [Arg | RestArg], [X | Rest]) :- Scop =.. [P, Arg, X], call(Scop), mapcar(P, RestArg, Rest). În definirea scopului adaug s-a utilizat simbolul ;. Acesta este un operator predefinit în Prolog care marchează o disjuncţie de scopuri, deci are semnificaţia de disjuncţie logică. Efectul operatorului ; poate fi reprezentat în următorul fel: X ; X :- X. X ; Y :- Y. Deci aşa cum operatorul Prolog , corespunde unei conjuncţii de scopuri, operatorul ; corespunde unei disjuncţii de scopuri. Comportarea acestui program este următoarea: ?- mapcar(prim, [[alain, turing], [ada, byron]], Rez). Rez = [alain, ada] ?- mapcar(neg, [1, 2, 3, 4], Rez). Rez = [-1, -2, -3, -4] ?- mapcar(adaug, [maria, mihai, george, ana], Rez). maria este femeie sau barbat (f/b)? f mihai este femeie sau barbat (f/b)? b george este femeie sau barbat (f/b)? b ana este femeie sau barbat (f/b)? f Rez = [[dna, maria], [dl, mihai], [dl, george], [dna, ana]] Dacă se doreste execuţia unor prelucrări similare, dar pentru predicate Prolog care admit trei argumente, primele doua instanţiate şi cel de al treilea sintetizat de predicatul Pred pe baza primelor două, se poate defini de o maniera similară predicatul: mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez) Se va demonstra funcţionarea acestui predicat în cazurile în care predicatul Pred, care se mapează pe elementele listelor ListaArgPrim şi ListaArgSecund, este întâi plus(A, B, Rez) care calculează în Rez suma lui A şi B, apoi conc(List1, Lista2, ListaRez)
  • 52. 52 care concatenează două liste şi depune rezultatul în ListaRez. Argumentele predicatului Pred pot fi argumente atomice sau liste. conc([], L, L). conc([X|Rest], L, [X|Rest1]) :- conc(Rest, L, Rest1). plus(A, B, Rez) :- Rez is A + B. % mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez) - % evaluează predicatul Pred pentru fiecare pereche % de argumente, unul din ListaArgPrim, celălalt din ListaArgSecund % şi construieşte rezultatul în ListaRez. % Predicatul Pred are trei argumente, atomice sau liste. mapcar2( _ , [], _ , []). mapcar2(P, [Arg1 | RestArg1], [Arg2 | RestArg2], [X | Rest]) :- Scop =.. [P, Arg1, Arg2, X], call(Scop), mapcar2(P, RestArg1, RestArg2, Rest). ?- mapcar2(plus, [1, 2, 3, 4], [10, 20, 30, 40], Rez). Rez = [11, 22, 33, 44] ?- mapcar2(conc, [[1, 2], [a, b]], [[3, 4], [c, d]], Rez). Rez = [[1, 2, 3, 4], [a, b, c, d]] Se observă că este necesară definirea unui predicat de tip mapcar diferit pentru fiecare categorie de aritate N a predicatelor care pot instanţia legal Pred. Dacă se impune restricţia conform căreia predicatul Pred poate avea numai argumente atomice, atunci se poate defini predicatul mapcarnl(Pred, ListaListeArg, ListaRez) cu un efect similar. ListaListeArg este fie o listă de elemente atomice, dacă Pred are două argumente, dintre care primul instanţiat va fi obţinut ca elementul curent al acestei liste, fie o listă de liste, unde fiecare element listă din ListaListeArg conţine primele N-1 argumente atomice ale lui Pred. % mapcarnl(Pred, ListaListeArg, ListaRez) - % evaluează predicatul Pred cu primele N-1 argumente din lista % ListaListeArg şi construieşte rezultatul în ListaRez. % Predicatul Pred poate avea oricâte argumente, dar toate trebuie sa fie atomice. mapcarnl(_, [], []). Mapcarnl(P, [Arg|RestArg], [X|Rest]) :- (atomic(Arg), FormaScop = [P,Arg,X] ; not atomic(Arg), conc([P], Arg, Temp), conc(Temp, [X], FormaScop)), Scop =.. FormaScop, call(Scop), mapcarnl(P, RestArg, Rest). ?- mapcarnl(neg, [1, 2, 3, 4], Rez] Rez = [-1, -2, -3, -4]
  • 53. 53 ?- mapcarnl[[1, 10], [2, 20], [3, 30], [4, 40]], Rez). Rez = [11, 22, 33, 44]. • findall(X, Scop, Lista) Predicatul findall(X, Scop, Lista) are ca efect obţinerea tuturor termenilor X care satisfac predicatul Scop în baza de cunoştinţe a programului şi cumularea acestor termeni în lista Lista. Scop trebuie să fie instanţiat la un predicat Prolog în care, în mod normal, trebuie să apară X. Dacă Scop nu reuşeşte, findall reuşeşte, instanţiind Lista la lista vidă (această ultimă caracteristică este specifică mediului ARITY). Exemple: prieten(vlad, ana). prieten(vlad, george). prieten(vlad, mihai). prieten(liviu, ana). ?- findall(X, prieten(vlad, X), L). X = _0038, L = [ana, george, mihai] Efectul predicatului findall poate fi explicat şi prin definiţia explicită a unui predicat cu acelaşi efect: find_all(X, Scop, Lista) % find_all(Arg, Scop, Lista) - % are acelaşi efect ca predicatul standard findall(Arg, Scop, Lista). prieten(vlad, ana). prieten(vlad, george). prieten(vlad, mihai). prieten(liviu, ana). find_all(X, S, _) :- asserta(stiva(baza_stivei)), call(S), % Scopul S conţine X. asserta(stiva(X)), fail. find_all( _ , _ , L) :- colecteaza([], M), !, L = M. colecteaza(S, L) :- urmator(X), !, colecteaza([X | S], L). colectează(L, L). următor(X) :- retract(stiva(X)), !, X = baza_stivei. ?- find_all(X, prieten(vlad, X), L). X = _0038, L = [ana, george, mihai]. ?- find_all(X, prieten(toma, X), L). X = _0038, L = []
  • 54. 54 În definiţia predicatului find_all se observă utilizarea unei tehnici de programare Prolog interesante. Utilizând predicatul asserta se simulează o structură de stivă în Prolog. Baza stivei este marcata de faptul stiva(baza_stivei) şi în stivă se introduc, ca fapte suplimentare adăugate la începutul bazei de cunoştinţe Prolog, valorile X care satisfac scopul S sub forma stiva(X), cu X instanţiat de apelul call(S). Acest lucru este executat pentru toate soluţiile posibile ale predicatului call(S) datorită predicatului fail introdus în finalul definiţiei primei clauze a predicatului find_all. În acest fel toate valorile lui X din baza de cunoştinţe Prolog care satisfac S sunt introduse în stiva simulată. Cea de a doua clauză a predicatului find_all, care se execută după ce nu mai există nici o posibilitate de resatisfacere a primeia, are ca scop golirea stivei şi colectarea valorilor introduse în stivă, până la baza ei, stiva(baza_stivei), în lista M. • bagof(X, Scop, Lista) Predicatul bagof(X, Scop, Lista) are ca efect colectarea în Lista a tuturor termenilor X care satisfac Scop, ţinând cont de diversele instanţieri posibil diferite ale celorlalte variabile din Scop. Scop este un argument care trebuie să fie instanţiat la un scop Prolog. Dacă Scop nu conţine alte variabile în afara lui X, atunci efectul predicatului bagof este identic cu cel al lui findall. Dacă Scop nu reuşeşte cel puţin o dată atunci bagof eşuează. Exemple: clasificare(a, vocala). clasificare(b, consoana). clasificare(c, consoana). clasificare(d, consoana). clasificare(e, vocala). clasificare(f, consoana). ?- findall(X, clasificare(X, C), L). X = _0038, C = _004C, L = [a, b, c, e, f] % o soluţie ?- bagof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [b, c, d, f]; X = _0038, C = vocala, L = [a, e] no % două soluţii • setof(X, Scop, Lista) Predicatul setof(X, Scop, Lista) are acelaşi efect cu predicatul bagof, cu excepţia faptului că valorile cumulate în lista Lista sunt sortate crescător şi se elimină apariţiile multiple de valori, deci Lista devine o mulţime ordonată.Dacă Scop nu reuşeşte cel puţin o dată atunci setof eşuează. Considerând faptele de definire a consoanelor şi variabilelor prezentate anterior, se poate construi următorul exemplu, în care mai introducem două fapte:
  • 55. 55 asserta(clasificare(d, consoana)). asserta(clasificare(e, vocala)). ?- bagof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [d, b, c, d, f]; % consoana d apare de două ori X = _0038, C = vocala, L = [e, a, e]; % vocala e apare de două ori no ?- setof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [b, c, d, f]; % consoanele apar o singură dată X = _0038, C = vocala, L = [a, e]; % vocalele apar o singură dată no 4.4 Direcţia de construire a soluţiilor Majoritatea programelor Prolog se bazează pe recursivitate. Ca în orice limbaj de programare recursiv, parametrii de ieşire ai subprogramelor, deci argumentele sintetizate ale predicatelor în cazul limbajului Prolog, pot fi calculate fie pe ramura de avans în recursivitate, fie pe ramura de revenire din recursivitate. Se consideră două variante de definire a predicatului de numărare a elementelor dintr-o listă. Prima varianta, nr_elem(Lista, NrElem), construieşte soluţia (calculează numărul de elemente din listă) pe ramura de revenire din recursivitate. % nr_elem(Lista, NrElem) nr_elem([], 0). nr_elem([ _ | Rest], N) :- nr_elem(Rest, N1), N is N1 + 1. A doua variantă, nr_elem2(Lista, NrElem) calculează numărul de elemente din listă pe ramura de avans în recursivitate. Pentru a realiza acest lucru, se foloseşte un predicat ajutător nr_elem1(Lista, NrAcumulat, NrElem) care are un argument suplimentar faţă de predicatul nr_elem2. Acest argument, NrAcumulat, are rolul de variabilă de acumulare a numărului de elemente din listă pe măsura avansului în recursivitate şi este instanţiat la primul apel la valoarea 0. În momentul atingerii punctului de oprire din recursivitate, deci în cazul în care lista ajunge vidă, valoarea acumulata în argumentul NrAcumulat este copiată în cel de-al treilea parametru al predicatului nr_elem_1. Se face apoi revenirea din apelurile recursive succesive fără a efectua nici o altă prelucrare, predicatul nr_elem_1 reuşeşte şi trimite valoarea calculată în NrElem predicatului iniţial nr_elem2. % nr_elem2(Lista, NrElem) % nr_elem1(Lista, NrAcumulat, NrElem) nr_elem2(Lista, N) :- nr_elem1(Lista, 0, N). nr_elem1([], N, N). nr_elem1([ _ | Rest], N1, N2) :- N is N1 + 1, nr_elem1(Rest, N, N2).