Introducere practică în Prolog

Embed Size (px)

Citation preview

Introducere practic n Prolog (I)n programarea logic, ideea de baz este exprimat prin Algorithm = Logic + Control. Componenta Logic - asimilat cu "program Prolog", sau "baz de date" - vizeaz descrierea problemei: ce fapte se cunosc (aseriuni iniiale asupra obiectelor problemei) i ce reguli generale ("parametrizate" fa de obiectele problemei) trebuie folosite pentru a deduce ulterior alte fapte (noi). Componenta Control reprezint nsui interpretorul (ori fiina raional) care va "executa" programele; interpretorul de Prolog "aplic" metoda deducerii (plecnd de la baza de date furnizat de program) i un mecanism intern de backtracking recursiv - cutnd sistematic valori cu care s "nlocuiasc" necunoscutele implicate n scopul ("goal") satisfacerii clauzelor curente. Sunt n uz diverse implementri ale componentei "Control" (vezi Prolog Systems) i n mare ele respect un acelai standard (ISO Prolog). Aici avem n vedere i utilizm SWI-Prolog.

O exemplificare clasic: relaii de rudenien privina ordinii exemplificrilor cuprinse n lucrri introductive de Prolog - prezentarea unei baze de date pe o tem larg cunoscut - cum ar fi, relaiile de rudenie - este clasic (la fel cu programele "Hello world!" cu care debuteaz prezentarea n alte limbaje). Prinii lui Fred sunt Ed i Edna (vezi The Flintstones); Pebbles este fiica lui Fred; etc.man('Fred'). man('Ed').% exprim faptul c "Fred este brbat"

woman('Edna'). % Edna este femeie woman('Wilma'). woman('Pebbles'). parent('Ed', 'Fred'). % Fred este un copil al lui Ed parent('Edna', 'Fred'). parent('Fred', 'Pebbles'). parent('Wilma', 'Pebbles'). grandparent(X, Z) :% X este bunic/bunic a lui Z dac (notat :-): parent(X, Y) % exist Y nct este valabil parent(X, Y) , % i (virgula conjug clauzele) parent(Y, Z). % este satisfcut parent(Y, Z). Am declarat nite fapte - man('Fred', woman('Wilma') etc. - nite relaii - parent(Ed,Fred) etc. - i o regul (pentru a fi "grandparent"). Caracterul . (denumit "full-point") ncheie declaraia.

'Fred' - cu apostrof - este o constant (alternativa era fred, cu litere mici dar fr ncadrare cu apostrof); Fred - cu majuscul iniial i nencadrat de apostrof - ar fi interpretat ca o variabil (cum sunt mai sus X, Y, Z). Interpretorul va substitui o variabil cu un obiect sau altul, dup caz. S lansm interpretorul SWI-Prolog, swipl:vb@vb:~/docere/doc/Prolog$ swipl % library(swi_hooks) compiled into pce_swi_hooks 0.00 sec, 2,224 bytes Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.10.1) Copyright (c) 1990-2010 University of Amsterdam, VU Amsterdam SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details.

For help, use ?- help(Topic). or ?- apropos(Word). ?-

Fie "flintstones.pl" fiierul n care am salvat declaraiile de mai sus; la promptul ?- tastm:?- [flintstones]. % flintstones compiled 0.00 sec, 1,604 bytes true. ?-

Interpretorul a ncrcat i a compilat fiierul respectiv, afind din nou promptul. Putem pune ntrebri (interogri, "queries") i putem formula diverse scopuri (similar cu "program main()").?- man('Fred'). true. % acest fapt exist n baza de date ?- man('Mister'). false. % Nu exist (i nici nu poate fi dedus) ?- man(Fred). % aici Fred este o variabil! Fred = 'Fred' ; % se tasteaz ; pentru a obine rspunsuri alternative Fred = 'Ed'. ?-

Interogarea man(Fred) - unde Fred este o variabil (puteam folosi la fel de bine man(X), de exemplu) are drept rspunsuri toate valorile cu care se poate nlocui "necunoscuta" Fred astfel nct s fie satisfcut clauza "man". Interpretorul caut o potrivire n baza de date, afieaz rspunsul gsit

i apoi ateapt: dac se tasteaz ; (pentru "sau" soluie alternativ) atunci reia cutarea. n momentul ncrcrii i compilrii programului, interpretorul indexeaz intern termenii respectivi (dup nume i dup ordinea apariiei lor n program) - astfel c acum gsete foarte uor termenul "man" (mpreun cu "ramurile" respective: 'Fred' i 'Ed', n aceast ordine); gsind prima potrivire posibil - anume man(Fred) = man('Fred') - interpretorul marcheaz intern "ramura" respectiv astfel c (dup ce se tasteaz ;) cutarea unei alternative va fi reluat ncepnd din acest loc.?- parent(X, 'Pebbles'). X = 'Fred' ; % sau X = 'Wilma'.% Cine sunt prinii lui 'Pebbles'?

?- parent(X, 'Pebbles'), woman(X). X = 'Wilma'.

% Dar cine este mama lui 'Pebbles'?

Pentru ultima interogare de mai sus, interpretorul gsete nti "potrivirea" parent('Fred', 'Pebbles') i consider mai departe valoarea 'Fred' pentru toate apariiile lui X n cadrul interogrii; dar cu aceast instaniere a variabilei X, clauza woman('Fred') este nesatisfcut - "Fail" - i atunci revine - "Redo" - la cealalt "ramur", instaniind X cu 'Wilma':?- trace. true. [trace] Call: Exit: Call: Fail: Redo:% pentru a trasa pas cu pas execuia urmtoarei interogri

?- parent(X, 'Pebbles'), woman(X). (7) parent(_G793, 'Pebbles') ? creep (7) parent('Fred', 'Pebbles') ? creep (7) woman('Fred') ? creep (7) woman('Fred') ? creep (7) parent(_G793, 'Pebbles') ? creep

Exit: (7) parent('Wilma', 'Pebbles') ? creep Call: (7) woman('Wilma') ? creep Exit: (7) woman('Wilma') ? creep X = 'Wilma'.

Dac valorile unuia dintre argumentele prevzute n clauze sunt indiferente, atunci putem folosi o variabil anonim - reprezentat prin _:[trace] true. ?- notrace.% Anuleaz trasarea pas cu pas

[debug] ?- parent(X, _). % Care sunt prinii? X = 'Ed' ; % sau (se tasteaz ;) X = 'Edna' ; % sau X = 'Fred' ; % sau X = 'Wilma'. [debug] ?- grandparent(X, 'Pebbles'). X = 'Ed' ; X = 'Edna' ; false.% Cine sunt bunicii lui 'Pebbles'?

[debug] ?- findall(X, parent(X, _), Result_list). Result_list = ['Ed', 'Edna', 'Fred', 'Wilma']. [debug] ?- findall(X, grandparent(X, _), L). L = ['Ed', 'Edna'].

% List cu prinii

% List cu bunicii

?- explain(findall) va expune o documentare pentru predicatul predefinit respectiv (aici, findall); pentru documentare se poate folosi i help(findall), dar "explain" indic i fiierul-surs aferent

(uneori, aici gseti cea mai bun lmurire a lucrurilor!).

Am observat mai sus, dar este de subliniat: cutarea n baza de date se face "de sus n jos" - ceea ce nseamn c ordinea clauzelor este important - cu eventual revenire la precedentul punct n care exist alternative neexplorate; n cazul "execuiei" unei clauze (sau interogri) compuse din mai multe subclauze conjugate prin , - ordinea operrii este "de la stnga la dreapta" (iar aici"operare" nu nseamn "evaluare i atribuire" ca n alte limbaje, ci substituie i unificare, urmrind "potrivirea" final a termenilor).

Exemplificri aritmeticeProlog a aprut n 1972 i are tangene conceptuale mai degrab cu SQL (unde deasemenea folosim interogri i baze de date relaionale), dect cu limbajele "clasice" (Pascal a aprut n 1970, iar C n 1978). Pentru tratarea serioas a unor aspecte matematice, limbajele "clasice" (ncepnd cu limbajul de asamblare) sunt desigur cele mai potrivite; altfel ns, diverse aspecte aritmetice pot fi tratate n orice limbaj i este interesant de vzut asemenea exemple n Prolog i n SQL.

Cel mai mare divizor comunAmintim cum s-ar scrie o funcie Pascal care furnizeaz "Gcd":function GCD(a, b: integer): integer; begin if b = 0 then GCD := a else GCD := GCD(b, a mod b); end;

Se recunoate desigur, algoritmul lui Euclid i fiind formulat recursiv, poate fi reflectat uor n

Prolog (unde recursivitatea este "limba matern"):gcd(A, B, G) :(B =:= 0 -> G is A ; R is A mod B, gcd(B, R, G) ).% gcd(A,B) este G dac: % n cazul cnd B are valoarea 0, G = A % altfel, % (R fiind restul mpririi lui A la B), % avem G = gcd(B,R).

Am definit un predicat cu o singur clauz, gcd/3 (de aritate 3, adic avnd 3 argumente). Construcia ( clauz -> clauz_1 ; clauz_2 ). corespunde cu "IF clauz THEN clauz_1 ELSE clauz_2". Pentru cazul valorilor numerice, operatorii prevzui pentru egalitate, inegalitate i "mai mic sau egal" difer ca simbol de alte limbaje: =:= (egalitate), =\= (inegalitate), =< (mai mic sau egal).R is Expresie asigur evaluarea expresiei aritmetice din partea dreapt i eventual, instanierea variabilei R cu valoarea rezultat; forma echivalent este is(R, Expresie).

Lansm swipl i tastm la promptul interpretorului:?- [pitagora]. % consult fiierul pitagora.pl % pitagora compiled 0.00 sec, 592 bytes true. ?- listing. gcd(C, A, B) :( A=:=0 -> B is C% listeaz definiiile

; ). true.

D is C mod A, gcd(A, D, B)

?- gcd(12, 21, G). G = 3.

% o interogare

listing nlocuiete identificatorii de variabile din program cu cte o anumit majuscul.

Dar putem face i probe mai interesante dect am fcut mai sus (pe numerele 12 i 21):?- X is 3**20 * 5**10 * 7**12, Y is 3**25 * 7**8 * 11**11, gcd(X, Y, G). X = 471304534201247574228515625, % 320 * 510 * 712 Y = 1393590653142003761801219090073, % 325 * 78 * 1111 G = 20100618201669201. % cel mai mare divizor comun ?- G is 3**20 * 7**8. G = 20100618201669201.% verificare: cel mai mare divizor comun este 320 * 78

Pentru aritmetica numerelor ntregi sau raionale, SWI-Prolog - ca i alte interpretoare, pentru diverse limbaje - folosete biblioteca GMP (scris n C i limbaj de asamblare) - nct putem folosi "gcd" i pentru numere mari, cum am ilustrat mai sus. ns din punctul de vedere al definiiei "gcd", proba cea mai interesant este aceasta:?- gcd(0, 0, G). G = 0.

Deci se "ncalc" definiia uzual, n care cel mai mare divizor comun este definit cu excepia cazului cnd ambii operanzi sunt nuli. Corect ar fi fost ca gcd(0, 0, G) s fie "false" (nu "G = 0"), la

fel cum:?- gcd(12, 21, 5). false. ?- gcd(12, 21, 3). true.% "este adevrat c 5 este cel mai mare divizor pentru 12 i 21"? % Nu. % "este adevrat c 3 este cel mai mare divizor pentru 12 i 21"? % Da.

Tratarea acestui caz de excepie (omis i n funcia Pascal de mai sus) ar necesita de obicei instruciuni n plus n cadrul funciei, constituia "clasic" a unei funcii recursive fiind: verific "condiia de oprire"; dac este satisfcut, atunci returneaz un anumit rezultat i "exit" - altfel, determin noii parametri i reapeleaz funcia cu acetia. i pe de o parte, verificarea condiiei de oprire va decurge la fiecare nou reapelare, iar pe de alta - "exit" nseamn "curarea" succesiv a cadrelor-stiv create pe parcursul reapelrilor, revenind "nnapoi" la contextul iniial de apel al funciei recursive respective. Putem imita i n Prolog aceast manier "clasic" de tratare, dar dispunem i de o metod specific, n fond mai "direct" (i posibil, mai eficient): anume, putem specifica diverse cazuri particulare n clauze separate (dar "n cadrul" aceluiai predicat):gcd(A, 0, A) :- A > 0. gcd(A, B, G) :B > 0, R is A mod B, gcd(B, R, G).% dac A > 0, atunci gcd(A, 0) este A % gcd(A,B) este G dac: % B > 0 (n-a ajuns zero) i % (R fiind restul mpririi lui A la B), % avem G = gcd(B,R).

Acum predicatul "gcd" este format din dou clauze; condiiile A > 0 i B > 0 exclud cazul gcd(0, 0,

G) (acum obinem corect "false" i nu valoarea 0). n plus, am reuit astfel (separnd clauzele) s evitm construcia IF-Then-Else (care este n general costisitoare, ca timp de execuie). Lucrurile nu decurg totui dup cum ar fi de dorit:[trace] ?- gcd(2, 0, 2). Call: (6) gcd(2, 0, 2) ? creep % gsete clauza gcd(A, 0, A), "nlocuind" A cu 2 ^ Call: (7) 2>0 ? creep % "A > 0" ? (din prima clauz) ^ Exit: (7) 2>0 ? creep Exit: (6) gcd(2, 0, 2) ? creep true ; % prima clauz este satisfcut, dar exist alternative (tastm ;) Redo: (6) gcd(2, 0, 2) ? creep % gsete i a doua clauz, cu A 0 ? creep Fail: (6) gcd(2, 0, 2) ? creep false. % a doua clauz nu este satisfcut

Avnd scopul gcd(2,0,2), interpretorul a cutat n baza de date curent o declaraie potrivit scopului respectiv i a gsit nti regula cu antetul gcd(A,0,A) (prin nlocuirea "necunoscutei" A cu valoarea 2, capul acestei reguli devine identic scopului de satisfcut). Gsirea unei "potriviri" are de obicei, dou urmri: interpretorul reine intern locul n care a gsito (dac ulterior va fi cazul, va relua cutarea din acel loc) i respectiv, se trece la "executarea" corpului constituent al regulii gsite. n cazul redat mai sus, se execut (6) i apoi (7), ncheind cu rezultatul "true" verificarea primei reguli gsite. Dar dup ce afieaz acest rspuns, interpretorul trece n starea de ateptare: dac se tasteaz ; atunci interpretorul execut "Redo" - adic reia cutarea din precedentul punct n care a reinut c exist alternative (n cazul de fa, alternativa urmtoare n baza de date este

gcd(A,B,G) care se potrivete scopului prin substituia lui A cu 2, a lui B cu 0 i a lui G cu 2).

Este clar c execuia ar fi trebuit ncheiat imediat dup verificarea primei reguli. O metod uzual pentru a impune aceasta (influennd din afar, mecanismul intern de backtracking) const n folosirea operatorului denumit cut i notat prin ! (acesta elimin - sau "taie" - punctele de revenire precedente). Prima clauz se rescrie atunci astfel:gcd(A, 0, A) :- A > 0, !.% ("cut") ignor alternativele precedentului "Call" Cu aceast mic modificare (am adugat ! n prima clauz "gcd"), dac relum trasarea pentru

gcd(2,0,2) (redat mai sus pentru cazul fr "cut") - constatm c acum execuia se ncheie corect, adic imediat dup constatarea verificrii primei clauze.

Triplete pitagoreice primitiveS gsim triunghiurile dreptunghice distincte care au catetele numere naturale coprime; acestea sunt numite primitive Pythagorean triple (PPT). Vorbim deci de tripletele (a,b,c) de numere ntregi pozitive care satisfac relaiile: a < b, gcd(a, b) = 1 i c2 = b2 + a2 (unde gcd este predicatul definit mai sus, pentru cel mai mare divizor comun)."a < b" exclude triunghiurile congruente (triplete ca (3,4,5) i (4,3,5)), iar "a i b sunt coprime" exclude asemnarea (de exemplu (3k, 4k, 5k) n care k este un factor arbitrar).

O descriere brut a proprietii PPT poate fi urmtoarea:ppt(A, B, C) :% (A,B,C) este PPT (dar cu C b > a) (c > b > a)

ppt(N, Min) :- ppt(_, _, N, Min). % toate PPT (c,b,a) cu Min c N ppt(A, B, Max, Min) :-

between(Min, Max, C), % genereaz o valoare C nct Min C Max between(4, C, B), % 4 B C_curent between(3, B, A), % 3 A B_curent A^2 =:= (C - B) * (C + B), % ceva mai convenabil, dect C^2 =:= A^2 + B^2 gcd(A, B, 1), % A i B sunt coprime writeln((C, B, A)), fail. % afieaz soluia i reia ("Redo")

Dac vrem toate PPT pn la 100: ppt(100) (vezi imaginea alturat). Renunnd la clauza gcd(A,B,1) (o putem "comenta", prefixnd cu %) i repetnd - rencrcm fiierul i lansm ppt(100) vom obine toate cele 52 de triplete pitagoreice pn la 100 (nu numai pe cele 16 care sunt PPT). ns de exemplu pentru ppt(600, 500), rezultatul (lista PPT, ntre 500 i 600) se obine mult mai ncet. Aceasta, pentru c - fa de versiunea "brut" iniial - nu am prevzut dect "optimizri"

minore (nelegate de fondul problemei): am nlocuit "C^2 =:= A^2 + B^2" cu o expresie care se evalueaz ceva mai repede i pe de alt parte, am inversat locurile pentru aceast clauz i respectiv, clauza gcd(A,B,1) (ceea ce este totui important: pentru exemplul din imagine, gcd(A,B,1) se va apela acum numai pentru cele 52 de triplete pitagoreice). Un PPT (a,b,c) are cteva proprieti implicite foarte simple, de care am putea ine seama n vederea eficientizrii programului. a i b fiind coprime, rezult c a, b, c sunt prime dou cte dou i c este impar; deci between(Min, Max, C) ar trebui s genereze doar valorile C impare. Ptratele dau sau restul 0, sau restul 1 la mprirea prin 3; deci, reducnd modulo 3 egalitatea a2 + b2 = c2, avem sau 0+1=1, sau 1+0=1 (cazul 0+0=0 trebuie respins fiindc a,b,c sunt coprime; cazul 1+1=1 trebuie evident, respins). Rezult c sau a, sau b este divizibil cu 3 (iar c nu poate fi multiplu de 3). O analiz similar pentru resturile mpririi prin 4 arat c valorile C trebuie s fie nu numai impare (cum am vzut mai sus), dar chiar congruente cu 1 modulo 4. Analog avem: modulo 16, ptratele sunt 0, 1, 4, sau 9; analiznd cazurile posibile pentru a2 + b2 = c2 (modulo 16) rezult c fie a, fie b este multiplu de 4. Putem evita deocamdat, modificarea "radical" a programului de mai sus (ar trebui rescrisbetween, nct s genereze numai anumite valori - dar n feluri diferite pentru C, B i respectiv pentru A).

Putem sintetiza cele stabilite mai sus n dou clauze: produsul a*b se divide cu 12 i respectiv, C ia numai valori congruente modulo 4 cu 1:ppt(A, B, Max, Min) :-

Rencrcnd "pitagora.pl" putem constata c viteza de execuie se mrete de cteva ori, fa de prima versiune. De exemplu, lista PPT pn la 500 rezult cam n 8 secunde; dar mai departe pn la 1000, se ajunge totui la 1 minut:?- time(ppt(1000)). 5,4,3 13,12,5 . . . . . . 985,864,473 997,925,372% pentru documentare, ?- help(time).

between(Min, Max, C), % genereaz o valoare C nct Min