Table of Contents

Prolog: Introducere

Obiective

Scopul acestui laborator este introducerea în programarea logică și învățarea primelor noțiuni despre Prolog.

Aspectele urmărite sunt:

Motivație

Prolog

Prolog a fost unul dintre primele limbaje de programare logice și rămâne în continuare cel mai popular astfel de limbaj, folosit în demonstratoarele de teoreme și utilizat inclusiv pentru o parte din implementarea sistemului IBM Watson. Permite separarea datelor de procesul de inferență, programele fiind scrise într-un stil declarativ și uniform. Impactul principal l-a avut în cercetare, în domeniul inteligenței artificiale, dar a rămas un punct de inspirație pentru limbajele de după.

Numele limbajului este o abreviere pentru programmation en logique, și este bazat pe calcul cu predicate.

Recapitulare teorie

Logica cu predicate de ordin I este o extensie a logicii propoziționale, prin folosirea de variabile cuantificate pentru a stabili relații. Urmăriți următoarele două exemple de transformare din logica propozițională în cea cu predicate de ordin I.

Toții peștii respiră. (prin branhii)

∀ X . (peste(X) → respira(X))

Unii pești au o respirație aeriană. (prin plămâni)

∃ X . (peste(X) ∧ respira(X))

Limbajul restricționează această logică doar la folosirea de clauze Horn. O clauză este o disjuncție (o operație sau) peste predicate sau și negații de predicate. O clauză Horn conține un singur literal pozitiv, ceea ce înseamnă că este o implicație care nu poate avea drept concluzie o disjuncție între mai multe predicate. (Vezi cursul pentru o înțelegere mai bună.)

A_1 ∧ A_2 ∧ … ∧ A_n → A

true → B

Deci următoarea implicație nu poate fi transcrisă direct într-o regulă în Prolog, pentru că are ca implicație o disjuncție dintre două predicate.

int(a) ∧ int(b) ∧ a ≠ 0 ∧ sum(a, b) = 0 → negative(a) ∨ negative(b)

SWI-Prolog

SWI-Prolog este o implementare open-source a limbajului, dispunând de multe biblioteci, fiind un punct bun de plecare pentru tranziția către alte limbaje logice/implementări.

Dintre toate implementările fiecare are particularitățile ei sintatice sau de folosire. De aceea vă rugăm să urmăriți instrucțiunile de aici pentru laborator.

Sintaxă și semantică

Programele scrise în Prolog descriu relații exprimate prin clauze. Există două tipuri de clauze:

“Calculul” modelează în această paradigmă efectuarea de raționamente.

Axiome (Fapte)

Axiomele, sau faptele, sunt predicate de ordinul I de aritate n, considerate adevărate. Ele stabilesc relații între obiectele universului problemei. Exemple:

% Acesta este un comentariu.
% Predicate de aritate 1. (unare)
caine(cerberus).
om(socrate). 
muritor(leulDinNemeea).
muritor(rhesus).

% Predicate de aritate 2. (binare)
% cel_mai_bun_prieten(?Cine, ?AlCui)
cel_mai_bun_prieten(cerberus, hades).

% Predicate de aritate 3. (ternare)
% rege(?Nume, ?Regiune, ?Aliat)
rege(rhesus, tracia, troia).

Rulăm următoarele interogări:

?- caine(cerberus). % este cerberus un câine?
true.

?- muritor(socrate). % vom detalia mai jos
false.

Termeni

În Prolog orice valoare se numește termen. Tipuri simpli de termeni:

Cuvântul structură este un sinonim pentru termenul compus.

Puteți considera momentan că singura diferență, sintactică, este că predicatele nu sunt transmise ca argumente, aceasta fiind o discuție mai subtilă ce ține de reprezentarea internă a implementării.

Consultați **glosarul** pentru orice detalii suplimentare.

Scopuri și variabile

Când rulăm interogări despre termeni și relațiile dintre ei spunem informal că demonstrăm sau obținem informații pornind de la “baza noastră de date” (de la axiome, de la fapte).

Calcul se face prin încercarea de a satisface [^1] scopuri (en. goals).

OBSERVAȚIE: Când am interogat dacă Socrate este muritor, procesul de execuție a returnat false deoarece nu se putea satisface acest scop. Nu înseamnă că el este nemuritor. Aceasta este ipoteza lumii închise – orice nu poate fi demonstrat ca adevărat va fi considerat fals.

% Este Rhesus muritor și rege al Traciei, aliat al Troiei?
% Avem două scopuri de satisfăcut
?- muritor(rhesus), rege(rhesus, tracia, troia).
true.

% Cine este muritor?
?- muritor(X).       % tastăm o interogare cu un singur scop
X = leulDinNemeea ;  % tastăm ";" pentru a primi încă un răspuns
X = rhesus.

Observați că în a doua interogare am făcut primul nostru calcul util, folosind o variabilă, X. Argumentul nu mai este o valoare particulară, ci sistemul de execuție încearcă legarea ei la diferite constante sau atomi. Numele variabilelor (X) începe cu literă mare iar numele atomilor (leulDinNemeea, rhesus) începe cu literă mică.

Așa cum v-ați obișnuit de la Haskell, și Prolog permite folosirea de variabile anonime, _. Multiple folosiri ale lui _ nu se leagă la același termen.

?- muritor(X), rege(X, Y, _).
X = rhesus,
Y = tracia.

?- muritor(X), rege(X, _, _).
X = rhesus.

Reguli

Antet :- Corp.

O regulă este o declarație cu forma generală de mai sus, unde antentul este un predicat, iar corpul este alcătuit din premise separate de operatori. Ea se citește așa:

Antetul este adevărat dacă corpul este adevărat

Practic este o implicație de la dreapta la stânga.

Exemple:

om(socrate) :- true.
% Echivalent cu: om(socrate).

viu(hercule).   % semi-zeu, nici om, nici zeu
viu(zeus).      % regele zeilor de pe Muntele Olimp
viu(hunabku).   % tatăl zeilor în mitologia mayașă 

zeu(zeus).      % fapt adevărat în mitologia greacă

muritor(X) :- om(X).
muritor(X) :- viu(X), \+ zeu(X).

Observați că:

Rulăm următoarele interogări:

?- muritor(socrate).
true .

?- muritor(zeus).
false.

Procesul de execuție

Ca să începem să înțelegem cum se execută o interogare activăm modul trace.

?- trace.
true.

[trace]  ?- muritor(hercule).
   Call: (10) muritor(hercule) ? creep
   Call: (11) om(hercule) ? creep
   Fail: (11) om(hercule) ? creep
   Redo: (10) muritor(hercule) ? creep
   Call: (11) viu(hercule) ? creep
   Exit: (11) viu(hercule) ? creep
   Call: (11) zeu(hercule) ? creep
   Fail: (11) zeu(hercule) ? creep
   Redo: (10) muritor(hercule) ? creep
   Exit: (10) muritor(hercule) ? creep
true.

Observăm că mai întâi încearcă demonstrarea primei reguli pentru predicatul muritor și eșuează. Prima premisă din a doua regulă este adevărată (viu(X)). Observăm că eșecul demonstrației scopului zeu(hercule) determină adevărată a doua premisă (\+ zeu(X)). Cele două premise fiind puse în conjuncție, considerăm că Hercule este muritor.

Negația ca eșec în demonstrație

[trace]  ?- muritor(hunabku).
   Call: (10) muritor(hunabku) ? creep
   Call: (11) om(hunabku) ? creep
   Fail: (11) om(hunabku) ? creep
   Redo: (10) muritor(hunabku) ? creep
   Call: (11) viu(hunabku) ? creep
   Exit: (11) viu(hunabku) ? creep
   Call: (11) zeu(hunabku) ? creep
   Fail: (11) zeu(hunabku) ? creep
   Redo: (10) muritor(hunabku) ? creep
   Exit: (10) muritor(hunabku) ? creep
true.

În cazul lui Hunabku satisfacerea primei premise celei de-a doua reguli cât și eșecul demonstrației că este zeu, ne determină să îl considerăm muritor. Totuși deși grecii nu îl considerau zeu, el nu este un muritor, deci de unde contradicția?!

Folosirea operatorului \+ nu ne-a ajutat, întrucât el întoarce adevărat dacă nu se poate satisface argumentul, nu este echivalent cu operatorul boolean de negație.

Observație: Operatorul de negație not este deprecated.

Procesul de execuție 2

De asemenea, nu putem să “corectăm” greșeala anterioară prin “hardcodarea” valorii false, ca mai jos, întrucât procesul de execuție încearcă în ordinea din fișier toate declarațiile unui predicat până la satisfacerea acelui scop.

muritor(hunabku) :- false. % declarație ineficace

Deci regulile pentru muritor pot fi “condensate” într-una singură, prin folosirea operatorului sau ;.

muritor(X) :- om(X); viu(X), \+ zeu(X).

Aici apare prima distincție între logica formală și cea computațională: premisele din corpul unui reguli sunt parcurse de la stânga la dreapta când se satisface un scop. Deci dacă om(X) este adevărat, nu se mai caută satisfacerea a ce a rămas din corp. Presupunem că om(X) se evaluează la false, dacă viu(X) nu se poate satisface, nici nu se mai verifică \+ zeu(X)

V-ați obișnuit deja cu noțiunile exemplificate mai sus când verificați în C dacă un pointer la o structură nu este null înainte să îl dereferențiați.

// verificarea celei de-a doua condiții se efectuază doar dacă se trece de prima
if (ptr != NULL && ptr->field != ILLEGAL_VALUE) {
  // do something usefull
}

Operatori

Unificare

Înainte să explicăm “egalitatea”, este dezirabilă o discuție despre unificare, adică despre operatorul =.

Unificarea = procesul de identificare a valorilor variabilelor din 2 sau mai multe expresii, astfel încât substituirea variabilelor prin valorile asociate sa conducă la coincidența expresiilor
?- foo(a, B) = foo(A, b).
A = a,
B = b.

Diferitele tipuri de "egalitate"

Pentru o mai bună înțelegere, vom trata operatorii =, is, ==, \==, =:= și =\= din mai multe perspective.

În primul rând, din punct de vedere al necesității instanțierii variabilelor:

În al doilea rând, din exemplele de mai sus se deduce și ce tip de egalitate verifică fiecare dintre acești operatori:

?- X = 2 + 1.
X = 2+1.
?- 1 + 2 == 2 + 1.
false.
?- 2 + 1 == 2 + 1.
true.
?- X is 2 + 1.
X = 3.

?- 3 is 2 + 1.
true.

?- 3 is 1 + 2.
true.

?- 1 + 2 is 2 + 1.
false.

?- 2 + 1 is 2 + 1.
false.

?- 1 + 2 is 3.
false. 
?- 3 =:= 2 + 1.
true.

?- 1 + 2 =:= 3.
true.

?- 1 + 2 =:= 2 + 1.
true.

?- 3 =:= 3.
true.

Tipuri de date

Am discutat până acum de:

Liste

Sunt o colecție ordonată de termeni, identificată prin paranteze pătrate.

Șiruri

O secvență de caractere (un string) va fi înscrisă între ghilimele (").

?- X= "abc", string(X), writeln(X).
abc
X = "abc".

Documentarea predicatelor și a argumentelor

Pentru claritate, convenția pentru antetele predicatelor se scriu sub forma predicat/nrArgumente:

predicat(+Arg1, -Arg2, ?Arg3, ..., +ArgN)

Pentru a diferenția intrările (+) de ieșiri (-), se prefixează argumentele cu indicatori. Acele argumente care pot fi fie intrări, fie ieșiri se prefixează cu ?. Instanțierea parametrilor ține de specificarea acestora:

Resurse

Referințe

[^1]: În logica matematică, o formulă este satisfiabilă dacă este adevărată sub o anumită asociere de valori variabilelor sale.