Sisteme Expert

Nu esti logat.

Laboratorul 4


(Deadline: 23.04.2017 23:59:59)

Daca nu sunteti logati exercitiile nu se mai afiseaza.

Observatie: Adesea veti intalni o notatie de forma nume_predicat/numar. Numarul respectiv reprezinta numarul de parametri corespunzatori predicatului. Doua predicate cu acelasi nume dar cu numere diferite de parametri sunt predicate diferite.

Predicatul call

Predicatul call primeste ca parametru un termen compus si realizeaza o interogare corespunzatoare acelui termen, cu alte cuvinte a realiza call(p(X,Y)) e echivalent cu a face interogarea p(X,Y)

Predicatul once

Are aceeasi sintaxa ca si call, de exemplu, once(p(X,Y)), insa realizeaza o singura interogare pentru predicatul p(X,Y).

Directive

Conform specificatiilor din documentatia pentru Sicstus Prolog directivele sunt apeluri de predicate al caror rezultat nu este afisat. Se folosesc de obicei pentru predicate cu efecte colaterale precum afisari pe ecran, citire/scriere in fisier, consultari, incarcarea unui modul, definirea unui predicat dinamic etc. Ele trebuiesc precedate de :-.

Exemplu interogare obisnuita vs. directiva: | ?- X=3, write(X).
3
X = 3 ?
yes
| ?- :- X=3, write(X).
3
| ?-

In exemplul de mai sus, in primul caz, deoarece aveam in query o variabila, dupa terminarea evaluari interogarii a afisat si valoarea variabilei calculate si de asemenea si raspunsul yes care arata faptul ca predicatul s-a terminat cu succes. In al doilea caz, cand s-a folosit o directiva (observati ca e acelasi query de dinainte dar precedat de :- ), s-a realizat doar afisarea declansata de predicatul write atunci cand a fost evaluat insa nu s-au mai afisat nici valori de variabile si nici rezultatul predicatului. Totusi fiindca n-am primit niciun warning stim ca directiva s-a executat cu succes, daca insa ar fi rezultat cu un esec s-ar fi primit un mesaj asemanator cu cel de mai jos: | ?- :- X=3, write(X), fail.
3
* user:(X=3,write(X),fail) - goal failed
| ?-

In programele prolog directivele au aceeasi sintaxa si acelasi efect, si sunt executate la consultarea programului. De exemplu, pentru un program care contine linia de cod:

:-write('---acest program a fost consultat\n mesaj scris cu ajutorul unei directive---').

Rezultatul a fost: | ?- :-
consult('C:/Documents and Settings/userpc/Desktop/temporar.pl').
% consulting c:/documents and settings/userpc/desktop/temporar.pl...
---acest program a fost consultat
mesaj scris cu ajutorul unei directive---
% consulted c:/documents and settings/userpc/desktop/temporar.pl in module user, 0 msec -88 bytes
| ?-

Putem folosi directive de exemplu cand prin consultarea unui fisier dorim sa fortam si consultarea altor fisiere.
Consideram ca avem fisierele:
temporar1.pl, cu continutul:

:- consult(['C:/Documents and Settings/userpc/Desktop/temporar2.pl',
'C:/Documents and Settings/userpc/Desktop/temporar3.pl']).

p(t1).

temporar2.pl, cu continutul:
b(t2).

temporar3.pl, cu continutul:
a(t3).

La consultarea lui temporar1.pl avem urmatorul rezultat: | ?- consult('C:/Documents and Settings/userpc/Desktop/temporar1.pl').
% consulting c:/documents and settings/userpc/desktop/temporar1.pl...
% consulting c:/documents and settings/userpc/desktop/temporar2.pl...
% consulted c:/documents and settings/userpc/desktop/temporar2.pl in module user, 0 msec 16 bytes
% consulting c:/documents and settings/userpc/desktop/temporar3.pl...
% consulted c:/documents and settings/userpc/desktop/temporar3.pl in module user, 0 msec 16 bytes
% consulted c:/documents and settings/userpc/desktop/temporar1.pl in module user, 0 msec 224 bytes
yes
| ?- listing.
a(t3).

b(t2).

p(t1).

yes
| ?-

Observam ca daca schimbam continutul lui temporar2.pl in

p(t2).

obtinem rezultatul: | ?- consult('C:/Documents and Settings/userpc/Desktop/temporar1.pl').
% consulting c:/documents and settings/userpc/desktop/temporar1.pl...
% consulting c:/documents and settings/userpc/desktop/temporar2.pl...
% consulted c:/documents and settings/userpc/desktop/temporar2.pl in module user, 0 msec 16 bytes
% consulting c:/documents and settings/userpc/desktop/temporar3.pl...
% consulted c:/documents and settings/userpc/desktop/temporar3.pl in module user, 0 msec 16 bytes
The procedure p/1 is being redefined.
Old file: c:/documents and settings/userpc/desktop/temporar2.pl
New file: c:/documents and settings/userpc/desktop/temporar1.pl
Do you really want to redefine it? (y, n, p, s, a, b, or ?) y
% consulted c:/documents and settings/userpc/desktop/temporar1.pl in module user, 0 msec 120 bytes
yes
| ?- listing.
a(t3).

p(t1).

yes
| ?-

Aceasta se intampla deoarece daca avem incarcate niste predicate in urma consultarii unui fisier, si apoi incercam consultarea unui al doilea fisier care contine un predicat cu nume si numar identic de parametri fata de un predicat deja existent (in urma primei consultari), se considera ca predicatul trebuie redefinit (toate clauzele vechi sterse si in locul lor incarcate cele din fisierul nou), si se cere o confirmare pentru acest lucru. In cazul predicatelor cu format identic, observam, deci, ca operatia de consultare nu mai are comportament cumulativ (spre deosebire de cazul in care un predicat cu acel format nu a mai fost intalnit si clauzele lui se adauga in mod direct la baza de cunostinte existenta).

Observati ca inainte de a se defini predicatul p/1 din temporar1.pl, s-a realizat consultarea lui temporar2.pl. In urma consultarii lui s-a adaugat la baza de cunostinte clauza p(t2). Apoi dupa ce a terminat consultarea celor doua fisiere din lista, a ajuns sa defineasca si clauza p(t1). Cum deja era definit din temporar2.pl un predicat de forma p/1, s-a considerat ca este redefinit in temporar1.pl, s-a cerut confirmarea pentru acest lucru, si apoi, dupa cum se vede clauza veche p(t2) a fost inlocuita de definitia noua p(t1).

In cazul meniului de redefinire a unui predicat daca se tasteaza comanda ? se pot vedea explicatiile pentru toate optiunile: Do you really want to redefine it? (y, n, p, s, a, b, or ?) ?
       y    redefine this procedure
       n    don't redefine this procedure
       p    redefine this procedure and don't ask again
       s    don't redefine this procedure and don't ask again
       a    abort
       b    break
       ?    print this information

Uneori insa nu dorim redefinirea predicatelor din diverse fisiere, ci pur si simplu vrem sa cumulam regulile din mai multe fisiere. Adica in caz ca avem clauze definite in fisiere diferite pentru un predicat cu acelasi nume si numar de parametri sa consideram ca sunt de fapt clauzele aceluiasi predicat pe care acum vrem sa le adunam laolalta. In acest caz se va folosi directiva include.

Directiva include poate primi o singura cale de fisier sau mai multe, caz in care acestea trebuie puse intr-o lista.
:-include('cale_fisier').
:-include(['cale_fisier_1','cale_fisier_2',...,'cale_fisier_n']).

Sa consideram un exemplu de folosire a lui include. Avem fisierul exemplu_include1.pl cu codul:

a(1).
a(2).

b(1).
b(2).
Avem si fisierul exemplu_include2.pl, care il include pe primul, cu codul:
:-include('C:/Documents and Settings/userpc/Desktop/exemplu_include1.pl').

b(1).
b(3).
b(4).

c(1).
c(2).
Sa observam ce se intampla la consultarea fisierelor | ?- :-
consult('C:/Documents and Settings/userpc/Desktop/exemplu_include2.pl').
% consulting c:/documents and settings/userpc/desktop/exemplu_include2.pl...
% including c:/documents and settings/userpc/desktop/exemplu_include1.pl...
% included c:/documents and settings/userpc/desktop/exemplu_include1.pl in module user, 0 msec -32 bytes

% consulted c:/documents and settings/userpc/desktop/exemplu_include2.pl in module user, 0 msec -88 bytes
| ?- listing.
a(1).
a(2).

b(1).
b(2).
b(1).
b(3).
b(4).

c(1).
c(2).

yes
| ?-

Directiva include se comporta ca si cum ar copia clauzele din fisierul/fisierele incluse in cadrul programului chiar in locul unde apare directiva. In cazul de mai sus, observam ca fisierul inclus, exemplu_include1.pl, se termina cu definirea predicatului b/1. In cadrul fisierului exemplu_include2.pl, imediat sub directiva de includere a fisierului exemplu_include1.pl sunt definite tot clauze ale predicatului b/1. Sa consideram ca modificam programul exemplu_include2.pl astfel:

b(1).
b(3).
b(4).

c(1).
c(2).

:-include('C:/Documents and Settings/userpc/Desktop/exemplu_include1.pl').
Am mutat directiva la finalul fisierului. Acum sa observam outputul: | ?- consult('C:/Documents and Settings/userpc/Desktop/exemplu_include2.pl').
% consulting c:/documents and settings/userpc/desktop/exemplu_include2.pl...
% including c:/documents and settings/userpc/desktop/exemplu_include1.pl...
* clauses for user:b/1 are not together
* Approximate lines: 3-5, file: 'c:/documents and settings/userpc/desktop/exemplu_include1.pl'

% included c:/documents and settings/userpc/desktop/exemplu_include1.pl in module user, 0 msec 448 bytes
% consulted c:/documents and settings/userpc/desktop/exemplu_include2.pl in module user, 0 msec 688 bytes
yes
| ?- listing.
a(1).
a(2).

b(1).
b(3).
b(4).
b(1).
b(2).

c(1).
c(2).

yes
| ?-
Marcat cu galben e un warning pe care il primim atunci cand intre clauzele care definesc un anume predicat sunt intercalate clauzele altuia sau altor predicate. Acesta apare deoarece cand fisierul este inclus, clauzele pentru a/1 si b/1 sunt adaugate dupa clauzele lui c/1 (observati fisierele). Deci practic intre clauzele lui b se gasesc definite alte doua predicate. Fiind doar un warning programul e consultat cu succes iar listing dovedeste acest lucru, afisand clauzele din ambele fisiere. Observam ca in listing clauzele lui b/1 apar impreuna dar asta deoarece listing afiseaza predicatele in ordine alfabetica, insa, bineinteles, pastreaza ordinea clauzelor aceluiasi predicat (a se vedea ca b(1) si b(2) care provin din fisierul inclus sunt afisate ultimele printre clauzele lui b/1).

Prin urmare atentie la pozitia in care includeti un fisier in altul; dupa cum stiti ordinea clauzelor unui predicat este importanta pentru logica programului.

Predicate dinamice

Pana in prezent am lucrat doar cu predicate statice. Acestea se folosesc pentru a defini fapte si reguli care nu trebuie sa se schimbe pe parcursul executiei, in functie de starea sistemului (de aceea ele si sunt protejate la adaugare ori stergere de clauze: cu assert si retract veti obtine un permission error, iar pentru abolish trebuie precizata o anumita optiune ca sa functioneze, se va vedea mai jos). Insa exista si predicate dinamice, care pot avea clauze adaugate sau sterse in urma unor interogari. Definirea unui predicat dinamic se face cu ajutorul unei directive de forma:

:-dynamic nume_predicat/nr_argumente.
De exemplu:
:-dynamic p/3.
defineste faptul ca p folosit cu 3 argumente este un predicat dinamic. Atentie pot exista in program si predicate p cu numar de argumente diferit de 3 de exemplu cu 1, 2 4 etc. In caz ca nu a existat o directiva dynamic si pentru acestea, ele sunt statice (fiindca un predicat e unic identificat atat prin nume cat si prin numarul de argumente).

Pentru a verifica daca un predicat este sau nu declarat dinamic se poate folosi predicate_property. | ?- predicate_property(p(_,_,_),dynamic).
yes

Inserarea si stergerea faptelor din baza de cunostinte

Inserarea de clauze de la tastatura (in consola)

Acest lucru se poate face de la consola de sicstus prolog scriind [user]. inserand faptele si regulile si apoi apasand Ctrl+Z

pentru Sicstus 3:
| ?- [user].
| spune(pisica,'miau miau').
| {user consulted, 0 msec, 0 bytes}
yes
pentru Sicstus 4:
| ?- consult(user).
% consulting user...
| spune(pisica,'miau miau').
|
% consulted user in module user, 0 msec 200 bytes
yes
| ?-

Atentie aici se comporta ca si cum ar consulta un alt fisier, doar ca e scris de voi de la tastatura in consola. Puteti din gresala sa va redefiniti predicate deja existente. Daca ar fi existat in baza de cunostinte clauza spune(soricel,chit) inainte de a consulta clauza spune(pisica,'miau miau') atunci ar fi aparut un mesaj in care va cerea confirmarea pentru a redefini predicatul si prima clauza ar fi fost stearsa, in urma consultarii regasindu-se doar a doua clauza.

Nu prea e indicat sa scrieti clauze de predicate in felul acesta, de la consola, deoarece la inchiderea consolei daca nu v-ati salvat baza de cunostinte intr-un fisier ati pierdut toate clauzele.

Observatie: predicatele definite astfel sunt statice (in caz ca nu s-a specificat printr-o directiva faptul ca sunt dinamice), deoarece ceea ce introduceti de la tastatura e vazut precum continutul unui fisier oarecare pe care il consultati numai ca, in acest caz, acest continut e luat direct din consola.

Predicatele assert, asserta, assertz, retract, retractall

Aceste predicate lucreaza doar cu predicate dinamice.

Predicatul assert adauga clauze la baza de cunostinte. De exemplu: | ?- assert(stare(ion,flamand)).
yes
| ?- listing(stare).
stare(ion, flamand).

yes
| ?- assert((mananca(ion,_):-stare(ion, flamand))).
yes
| ?- listing(mananca).
mananca(ion, _) :-
        stare(ion, flamand).

yes
| ?- mananca(ion,placinta).
yes
| ?-

Observati ca in cazul regulilor la assert apar doua randuri de paranteze, parantezele interioare avand rolul de a grupa regula sub forma unui singur termen, altfel apar erori de sintaxa. Exemplu: | ?- assert(mananca(ion,_):-stare(ion, flamand)).
! Syntax error
! , or ) expected in arguments
! in line 834
! assert ( mananca ( ion , _ )
! <<here>>
! :- stare ( ion , flamand ) ) .
| ?-

Predicatele asserta si assertz adauga clauze la inceputul, respectiv sfarsitul bazei de cunostinte. Exemplu: | ?- listing(stare).
stare(ion, flamand).

yes
| ?- asserta(stare(luca,flamand)).
yes
| ?- listing(stare).
stare(luca, flamand).
stare(ion, flamand).

yes
| ?- assertz(stare(andrei,satul)).
yes
| ?- listing(stare).
stare(luca, flamand).
stare(ion, flamand).
stare(andrei, satul).

yes
| ?-

Predicatul retract sterge o singura clauza al carei head se potriveste (se poate realiza o unificare) cu termenul compus pe care il primeste ca parametru. Predicatul retractall sterge toate predicatele care indeplinesc acea conditie. Atentie, daca retract nu are ce sterge, returneaza no, pe cand retractall, indiferent daca sterge ceva sau nu, returneaza yes.

Sa luam ca exemplu programul de mai jos (pentru cei care nu stiu de unde vine catz din program, este ceea ce spunem cand vrem sa gonim o pisica, iar pis-pis cand vrem sa o chemam):

:-dynamic pisica/2.

%pisica(nume,culoare)
pisica(miau,alb).
pisica(mitzi,alb).
pisica(miaunel,gri).
pisica(pufi,gri).
pisica(X,Y):-miauna(X),culoare(Y).

spune(catz,X):-retract(pisica(X,_)).
spune(catz):-retractall(pisica(_,_)).
spune(catz,culoare,C):-retractall(pisica(_,C)).

spune(pis-pis,X,C):-assert(pisica(X,C)).
Observati acum interogarile de mai jos. Prima interogare, spune(catz,miau)., "goneste" din baza de cunostinte pisica ce poarta numele miau (se observa din listing faptul ca a disparut). Urmatoarea interogare, spune(catz) "goneste" toate pisicile din baza de cunostinte datorita termenului foarte general dat ca parametru lui retractall (in cadrul definirii lui spune(catz)) si anume pisica(_,_) care se poate unifica si cu toate faptele din baza de cunostinte, dar si cu head-ul regulii pisica(X,Y):- ... . | ?- listing(pisica).
pisica(miau, alb).
pisica(mitzi, alb).
pisica(miaunel, gri).
pisica(pufi, gri).
pisica(A, B) :-
        miauna(A),
        culoare(B).

yes
| ?- spune(catz,miau).
yes
| ?- listing(pisica).
pisica(mitzi, alb).
pisica(miaunel, gri).
pisica(pufi, gri).
pisica(A, B) :-
        miauna(A),
        culoare(B).

yes
| ?- spune(catz).
yes
| ?- listing(pisica).
yes
| ?-

Observam ca daca cerem in continuare detalii despre predicat: | ?- predicate_property(pisica(_,_),dynamic).
yes
| ?-
desi nu mai exista clauze ale predicatului pisica il recunoaste in continuare ca fiind dinamic.

Reconsultam programul ca sa mai observam ceva. Avem din nou toate pisicile in baza de cunostinte si incercam de data asta sa le gonim dupa culoare.

| ?- listing(pisica).
pisica(miau, alb).
pisica(mitzi, alb).
pisica(miaunel, gri).
pisica(pufi, gri).
pisica(A, B) :-
        miauna(A),
        culoare(B).

yes
| ?- pisica(X,gri).
X = miaunel ? ;
X = pufi ? ;
! Existence error in user:miauna/1
! procedure user:miauna/1 does not exist
! goal: user:miauna(_120)
| ?- spune(catz,culoare,gri).
yes
| ?- listing(pisica).
pisica(miau, alb).
pisica(mitzi, alb).

yes
| ?-

Observam ca pentru interogarea pisica(X,gri) intoarce bine solutiile pentru fapte, dar, atunci cand ajunge la regula, deoarece predicatul miauna/1 nu a fost definit, da o eroare. Totusi in urma interogarii spune(catz,culoare,gri) care apeleaza

spune(catz,culoare,C):-retractall(pisica(_,C)).
deci pentru acesta interogare practic apeleaza retractall(pisica(_,gri)), C-ul fiind instantiat cu gri, se sterge si regula care la interogare dadea eroare (cea care in listing apare cu pisica(A,B):- ...). Acest lucru se intampla deoarece retract si retractall, cum scria si mai sus realizeaza unificarea doar cu head-ul regulii, nu realizeaza efectiv o interogare pentru acel head (interogare care dupa cum s-a vazut ar fi dat chiar eroare, nicidecum sa se termine cu yes).

Atentie! In momentul in care Prologul ajunge la o instructiune retract, vede la acel pas care sunt faptele dinamice care pot fi sterse, si deci care ar fi solutiile lui retract. Sa prespunem ca in cadrul acelei reguli imediat dupa retract se face un assert cu o regula ce ar putea fi stearsa de catre retract. Aceasta a fost adaugata dupa momentul evaluarii solutiilor posibile ale lui retract. Deci daca am realiza intoarceri repetitive din backtracking (fie din cauza unui esec mai departe in clauza, fie prin cererea de noi solutii) dupa ce retract ar sterge toate faptele vechi (care au existat inaintea executarii assert-ului despre care vorbeam), nu ar mai sterge si faptele adaugate de assert, deoarece ele nu fac parte din setul initial de solutii, calculat la inceput. Pentru a intelege mai bine sa luam urmatorul exemplu:

:-dynamic pas/1.

stare_pred_dinam:- retractall(pas(_)), /*sterg orice fapt anterior al predicatului pas/2 */
                          assert(pas(ceva_1)), assert(pas(ceva_2)), /*adaug faptele pas(ceva_1) si pas(ceva_2) */
                          format('Faptele initiale:\n',[]),
                          listing(pas),
                          retract(pas(P)),
                          format('Am sters pas(~p)\n',[P]),
                          assert(pas(pas_nou)),
                          format('Afisare fapte dupa adaugare:\n',[]), listing(pas),
                          fail.
Afiseaza: | ?- stare_pred_dinam.
Faptele initiale:
pas(ceva_1).
pas(ceva_2).

Am sters pas(ceva_1)
Afisare fapte dupa adaugare:
pas(ceva_2).
pas(pas_nou).

Am sters pas(ceva_2)
Afisare fapte dupa adaugare:
pas(pas_nou).
pas(pas_nou).

no
| ?-

Initial din baza de cunostinte au fost complect sterse faptele predicatului pas/1 (reamintesc /1 se refera la numarul de parametri ai predicatului), cu un retractall, ca sa stim ca initial nu exista nimic definit pentru pas. Apoi se adauga doua fapte, cu assert: pas(ceva_1).
pas(ceva_2).
Acestea sunt si afisate in cadrul programului, cu ajutorul lui listing: Faptele initiale:
pas(ceva_1).
pas(ceva_2).
Apoi urmeaza instructiunea retract(pas(P)) pentru care Prologul observa ca in acest moment exista doar doua solutii posibile: pas(ceva_1) si pas(ceva_2). Il sterge pe primul, ceva_1, asa cum si este afisat: Am sters pas(ceva_1) In program, urmeaza instructiunea assert(pas(pas_nou)), de aceea listingul va afisa: pas(ceva_2).
pas(pas_nou).
Instructiunea fail de la final, va intoarce procesul de backtracking pana la instructiunea retract(pas(P)), deoarece format, listing si assert din fata lui se valideaza o singura data (din cauza asta, backtracking-ul, la incercarea de revalidare a fiecaruia, va fi trimis la instructiunea anterioara din regula). Astfel se sterge un nou fapt, si bactrackingul cu noua valoare calculata in P porneste inainte in regula, si se ajunge la format-ul de dupa retract care afiseaza: Am sters pas(ceva_2)
Se ajunge iar la assert si se mai adauga un fapt pas(pas_nou), in final listing-ul afisand numai faptele noi: Afisare fapte dupa adaugare:
pas(pas_nou).
pas(pas_nou).
Se ajunge iar la fail, insa motorul de backtracking stie ca niciunul dintre predicatele dinaintea failului nu mai are solutii (singurul care putea sa mai aiba era retractul, dar pentru el s-au calculat solutiile la inceput, si faptele noi nu mai sunt luate in considerare). Astfel predicatul are ca rezultat: no

Pentru a arata si mai clar pasii, mai jos s-a aplicat un trace pe predicat: | ?- stare_pred_dinam.
        1      1 Call: stare_pred_dinam ?
        2      2 Call: retractall(user:pas(_768)) ?
        2      2 Exit: retractall(user:pas(_768)) ?
        3      2 Call: assert(user:pas(ceva_1)) ?
        3      2 Exit: assert(user:pas(ceva_1)) ?
        4      2 Call: assert(user:pas(ceva_2)) ?
        4      2 Exit: assert(user:pas(ceva_2)) ?
        5      2 Call: format('Faptele initiale:\n',user:[]) ?
Faptele initiale:
        5      2 Exit: format('Faptele initiale:\n',user:[]) ?
        6      2 Call: listing(user:pas) ?
pas(ceva_1).
pas(ceva_2).
        6      2 Exit: listing(user:pas) ?
        7      2 Call: retract(user:pas(_815)) ?
?       7      2 Exit: retract(user:pas(ceva_1)) ?
        8      2 Call: format('Am sters pas(~p)\n',user:[ceva_1]) ?
Am sters pas(ceva_1)
        8      2 Exit: format('Am sters pas(~p)\n',user:[ceva_1]) ?
        9      2 Call: assert(user:pas(pas_nou)) ?
        9      2 Exit: assert(user:pas(pas_nou)) ?
       10      2 Call: format('Afisare fapte dupa adaugare:\n',user:[]) ?
Afisare fapte dupa adaugare:
       10      2 Exit: format('Afisare fapte dupa adaugare:\n',user:[]) ?
       11      2 Call: listing(user:pas) ?
pas(ceva_2).
pas(pas_nou).

       11      2 Exit: listing(user:pas) ?
        7      2 Redo: retract(user:pas(ceva_1)) ?
        7      2 Exit: retract(user:pas(ceva_2)) ?
       12      2 Call: format('Am sters pas(~p)\n',user:[ceva_2]) ?
Am sters pas(ceva_2)
       12      2 Exit: format('Am sters pas(~p)\n',user:[ceva_2]) ?
       13      2 Call: assert(user:pas(pas_nou)) ?
       13      2 Exit: assert(user:pas(pas_nou)) ?
       14      2 Call: format('Afisare fapte dupa adaugare:\n',user:[]) ?
Afisare fapte dupa adaugare:
       14      2 Exit: format('Afisare fapte dupa adaugare:\n',user:[]) ?
       15      2 Call: listing(user:pas) ?
pas(pas_nou).
pas(pas_nou).

       15      2 Exit: listing(user:pas) ?
        1      1 Fail: stare_pred_dinam ?
no
% trace

Atentie: O regula in timp ce este evaluata se poate sterge pe ea insasi (daca este, bineinteles, o regula a unui predicat dinamic). Observati interogarile de mai jos:

| ?- assert((p(X):-retractall(p(_)),write(ceva))).
true ?
yes
| ?- listing(p).
p(_) :-
retractall(user:p(_)),
write(ceva).

yes
| ?- p(1).
ceva
yes
| ?- listing(p).
yes
| ?-
Observati ca si dupa ce s-a facut retractall mai sus, restul predicatelor (adica write-ul) au fost evaluate in continuare.

Vom face un nou test:

| ?- assert((p(X):-retractall(p(_)),p(X),write(ceva))).
true ?
yes
| ?- listing(p).
p(A) :-
        retractall(user:p(_)),
        p(A),
        write(ceva).

yes
| ?- p(1).
no
| ?- listing(p).
yes
| ?-
Daca in regula nu ar fi existat retractall(p(_)), din cauza faptului ca p(X) apeleaza tot un p(X) ar fi intrat intr-o bucla infinita, dar realizarea lui retractall sterge regula lui p/1 din memorie, si desi regula curenta care practic tocmai a fost stearsa se executa in continuare (cum ati vazut in exemplul anterior) un nou apel catre aceasta va da no deoarece incearca o noua interogare si o cauta in baza de cunostinte dar nu o mai gaseste. Deci regula esueaza cand doreste sa evalueze p(X) de dupa retractall.

Reordonarea faptelor in baza de cunostinte

Uneori avem nevoie sa reordonam clauzele unui predicat dinamic dupa anumite considerente. De exemplu, sa presupunem ca avem faptele din exemplul de mai jos si vrem sa le ordonam crescator in functie de al doilea parametru.

:-dynamic a/3.

a(1,e,700).
a(4,q,500).
a(13,z,100).
a(2,b,300).

Nu putem sa mutam in mod direct fapte dintr-o pozitie in alta in cadrul bazei de cunostinte. Asadar cel mai simplu ar fi sa le stergem pe toate, punand structurile corespunzatoare lor intr-o lista. Lista respectiva o putem construi astfel incat ordinea elementelor sa fie chiar ordinea pe care o doream pentru faptele din baza de cunostinte. Apoi mai trebuie doar sa parcurgem lista respectiva si sa adaugam ca fapte structurile in ordinea in care le gasim in lista:

sortare_param2:-setof(a(B,A,C), retract(a(A,B,C)),L), format('Lista intermediara: ~p\n',[L]),bagof(a(A,B,C),(member(a(B,A,C),L),assertz(a(A,B,C))),L2), format('Lista faptelor in ordinea in care vor fi adaugate: ~p \n',[L2]). | ?- sortare_param2.
Lista intermediara: [a(b,2,300),a(e,1,700),a(q,4,500),a(z,13,100)]
Lista faptelor in ordinea in care vor fi adaugate: [a(2,b,300),a(1,e,700),a(4,q,500),a(13,z,100)]
yes
| ?- listing(a).
a(2, b, 300).
a(1, e, 700).
a(4, q, 500).
a(13, z, 100).

yes
| ?-

Dar daca vrem acum sa sortam faptele dupa valoarea produsului dintre primul si ultimul parametru? Ca si mai derveme ar trebui sa avem un termen compus al carui prim parametru sa fie criteriul de sortare (in cazul de fata, produsul): st(Prod,a(A,B,C)). Astfel pe masura ce setof parcurge faptele si le sterge, calculam si produsul,asa cum se vede in predicatul de mai jos. Apoi parcurgem lista de structuri care a rezultat si adaugam in baza de cunostinte structuri de forma a(A,B,C) in oridinea in care le gasim in lista construita de setof.

sortare_produse:-setof(st(Prod,a(A,B,C)), (retract(a(A,B,C)),Prod is A * C ),L),
    format('Lista intermediara: ~p\n',[L]),
    bagof(a(A,B,C),Pr^(member(st(Pr,a(A,B,C)),L),assertz(a(A,B,C))),L2),
    format('Lista faptelor in ordinea in care vor fi adaugate: ~p \n',[L2]).
| ?- sortare_produse.
Lista intermediara: [st(600,a(2,b,300)),st(700,a(1,e,700)),st(1300,a(13,z,100)),st(2000,a(4,q,500))]
Lista faptelor in ordinea in care vor fi adaugate: [a(2,b,300),a(1,e,700),a(13,z,100),a(4,q,500)]
yes
| ?- listing(a).
a(2, b, 300).
a(1, e, 700).
a(13, z, 100).
a(4, q, 500).

yes
| ?-

Predicatul abolish

Predicatul abolish este folosit tot pentru a sterge clauzele unui predicat insa in afara de clauze sterge si orice alte specificatii privitoare la acesta. Spre deosebire de retract si retractall el poate sterge si predicate statice, daca se forteaza acest lucru printr-un al doilea parametru. Sintaxa pentru acest predicat este abolish(nume_predicat/aritate) sau abolish(nume_predicat/aritate,lista_optiuni). Consideram tot exemplul de mai devreme: | ?- listing(pisica).
pisica(miau, alb).
pisica(mitzi, alb).
pisica(miaunel, gri).
pisica(pufi, gri).
pisica(A, B) :-
        miauna(A),
        culoare(B).

yes
| ?- abolish(pisica/2).
yes
| ?- listing(pisica).
* listing(user:pisica) - no matching predicate
yes
| ?- predicate_property(pisica(_,_),dynamic).
no
| ?-
Observam ca au disparut toate clauzele pentru predicatul pisica, dar si insusi predicatul a fost sters din baza de cunostinte, fapt ce se observa atat din output-ul lui listing ("no matching predicate") cat si din faptul ca nici nu mai e vazut ca predicat dinamic. Comparati cu rezultatul obtinut mai sus in urma unui retractall care a sters toate clauzele predicatului pisica.

Abolish nu poate sterge in mod direct predicate statice: | ?- abolish(spune/3).
! Permission error: cannot abolish static user:spune/3
! goal: abolish(user:spune/3)
| ?-
Dar daca ii specificam in lista de optiuni force(true) va putea sterge si predicate statice: | ?- listing(spune/3).
spune(catz, culoare, A) :-
        retractall(user:pisica(_,A)).
spune(pis-pis, A, B) :-
        assert(user:pisica(A,B)).

yes
| ?- abolish(spune/3,[force(true)]).
yes
| ?- listing(spune/3).
* listing(user:spune/3) - no matching predicate
yes
| ?-

Stergerea bazei de cunostinte incarcate in consola

Dupa cum am vazut mai sus consultarea are un comportament cumulativ si daca lucram succesiv la mai multe pograme fara sa inchidem consola ajungem in final sa le avem pe toate incarcate ceea ce uneori poate genera erori. Cea mai simpla metoda de a reinitializa consola este inchiderea si redeschiderea ei. Nu exista un predicat predefinit sau vreo comanda care sa stearga toate predicatele incarcate in consola.
In schimb se poate defini unul:

curata_bc:-current_predicate(P), abolish(P,[force(true)]), fail;true.

Este un predicat fara parametri care sterge toate predicatele, inclusiv pe el. Intai apeleaza current_predicate(P); current_predicate calculeaza pe rand in P fiecare predicat definit in mod curent in baza de cunostinte. P-ul va avea ca valori termeni de forma nume_predicat/nr_parametri. Apoi valoarea lui P este transmisa lui abolish, care folosit cu [force(true)] sterge si predicatele statice, nu doar cele dinamice. Dupa abolish avem un fail care folosind stilul de functioneare al backtrackingului, forteaza reevaluarea predicatelor de dinainte, adica reintoarcerea la current_predicate (mai intai ar reveni la abolish, dar acolo nu are variabile calculate deci nu afecteaza cu nimic). In momentul cand a sters toate predicatele, current_predicate(P) va rezulta si el in esec, in acest caz se va intra pe ramura a doua a sau-ului adica la predicatul true care intoarce yes determinand si curata_bc sa se termine cu succes.

Problema predicatului de mai sus e insa faptul ca dispare si el in urma interogarii. Daca mai avem nevoie de el si nu vrem sa fie sters nu avem decat sa testam ca valoarea lui P sa fie diferita de curata_bc/0.

curata_bc:-current_predicate(P), P \== curata_bc/0, abolish(P,[force(true)]), fail;true.


If... else ... in prolog

Desi se poate simula cu ajutorul operatorilor logici ; , \+ uneori e mai usor sa folosim constructiile built-in speciale.

Simularea if..else.. cu ajutorul a ceea ce s-a invatat pana acum se facea sub forma:

conditie(...), prelucreaza_if(...); \+ conditie(...), prelucreaza_else(...);

Atentie, desi in unele cazuri merge sa nu mai punem si \+ conditie(...), acest lucru in general nu e corect.

Un exemplu in care putem sa eliminam negarea conditiei ar fi urmatorul predicat cu un parametru care afiseaza par daca parametrul e par si impar in caz contrar:

p(X):- X mod 2=:=0, write(par); write(impar).
Sa vedem si un exemplu de interogare:
| ?- p(2).
par
yes
| ?- p(3).
impar
yes
| ?-
Predicatul functioneaza corect, deoarece daca numarul e par intra pe primul caz si afiseaza "par", insa, daca numarul e impar, va esua conditia X mod 2=:=0 si atunci va ajunge pe cealalta ramura a lui sau afisand "impar".

Insa daca ne uitam la urmatorul exemplu:

%p1(+Numar, ?Paritate)
p1(X,Y):-X mod 2 =:= 0, Y=par; Y=impar.
vom observa imediat ca e gresit. Aici vrem sa verificam daca s-a asociat corect un numar cu paritatea sa, sau in cazul in care al doilea parametru e neinstantiat, sa calculam in acesta paritatea primului parametru.
| ?- p1(2,par).
yes
| ?- p1(2,impar).
yes
| ?- p1(2,X).
X = par ? ;
X = impar ? ;
no
| ?-
Predicatul are un comportament gresit. De exemplu, pentru interogarea p(2,impar) de ce credeti ca totusi returneaza yes? Initial intra pe prima ramura si trece de testul X mod 2 =:= 0, dar esueaza la Y=par, deci prima ramura a lui sau rezulta in esec. Apoi intra pe a doua ramura, unde nu exista niciun test cu privire la numar, iar Y=impar se termina cu succes, determinand, deci, predicatul sa se termine cu succes pentru interogarea p(2,impar).

Varianta corecta e testarea negatiei conditiei in a doua ramura a lui sau:

p2(X,Y):-X mod 2 =:= 0, Y=par; \+ (X mod 2 =:= 0), Y=impar.

In acest caz totusi mai exista o varianta de rezolvare a problemei folosind ! (cut), dar care e in acest caz un cut rosu:

p3(X,Y):-X mod 2 =:= 0,!, Y=par; Y=impar.
Aceasta e o practica des intalnita. Practic se pune un cut dupa conditie:
pred(ExprVar):-conditie(ExprVar1),!,prelucr_caz_cond_adevarata(ExprVar2);prelucr_caz_cond_falsa(ExprVar3)
unde ExpVar-urile pot fi o variabila sau un set de variabile.

In exemplul de mai sus p3 functioneaza pentru ca daca X e numar par, il instantiaza pe Y la valoarea par, si daca cerem o noua solutie pentru Y, cand se va intoarce din backtracking la cut, se va opri, afisand no fiindca nu mai gaseste (sau mai bine zis, nu mai cauta) alte solutii. Daca X e impar in urma testului X mod 2 =:=0 va trece automat pe ramura a doua a lui sau.

Sa vedem inca un exemplu similar. Daca conditia e indeplinita, se interogheaza q(X,Y), daca nu, se interogheaza z(X,Y).

z(1,a).
z(2,b).
z(3,z).
q(3,c).
q(3,cc).
q(4,d).

conditie(X):-X>2.
%pred(+X,-Y)
pred(X,Y):-conditie(X),!,q(X,Y);z(X,Y).
Cu interogarile:
| ?- pred(3,Y).
Y = c ? ;
Y = cc ? ;
no
| ?-

Totusi cut se poate folosi doar cand in cadrul expresiei conditiei nu se calculeaza valori pentru eventuale variabile ce se vor folosi mai tarziu in regula, sau ne intereseaza doar prima solutie pentru aceste variabile. De asemenea alt dezavantaj este faptul ca daca folosim cut nu mai putem trece la clauzele urmatoare ale predicatului definit.
Sa luam ca exemplu:

%proprietate_nr(+Nr,-P)
proprietate_nr(Nr,P):- integer(Nr),!, P='rational intreg'; P='rational neintreg'.
proprietate_nr(_,real).
Acest program doreste sa returneze in P caracteristicile numarului dat ca parametru, de la cea mai specifica pana la cea mai generala. Pentru simplitate am considerat doar trei caracteristici posibile (rational intreg, rational neintreg si real).
| ?- proprietate_nr(3,P).
P = 'rational intreg' ? ;
no
| ?- proprietate_nr(3.5,P).
P = 'rational neintreg' ? ;
P = real ? ;
no
| ?-
Observam ca din cauza cut-ului pentru numere intregi nu mai putem obtine si valoarea "real". Sa consideram un alt program:
animal(pisica).
animal(hamster).

detine(ion, caiet).
detine(ion, pisica).
detine(ion, hamster).
detine(ion, carte).
detine(george,calculator).

%ce_animale_are(+Nume,-Animal)
ce_animale_are(Nume,A):-detine(Nume,A1),animal(A1),!,A=A1;A=niciunul.
Conditia ar fi: daca pentru numele dat gasim ca detine un ceva (memorat in A1) si acel ceva este un animal, atunci A va avea valoarea lui A1. Daca pentru numele respectiv gasim ca nu este detinut niciun animal, atunci returnam in A valoarea "niciunul".
| ?- ce_animale_are(george,A).
A = niciunul ? ;
no
| ?- ce_animale_are(ion,A).
A = pisica ? ;
no
Observam totusi ca din cauza lui cut nu obtinem toate solutiile pentru A daca persoana respectiva detine mai multe animale.

Observatie: clauza de mai sus se putea scrie intr-adevar mai simplu:
ce_animale_are(Nume,A):-detine(Nume,A),animal(A),!;A=niciunul.
dar am vrut sa se vada clar partea de conditie, calculul lui A in cazul conditiei adevarate si calculul lui daca aveam conditie falsa .

Prin urmare cut-ul in anumite situatii, creeaza probleme si in acele cazuri trebuie sa folosim negarea conditiei.

ce_animale_are2(Nume,A):-detine(Nume,A1),animal(A1), A=A1; \+ (detine(Nume,A1),animal(A1)),A=niciunul.
Totusi acest mod de a scrie este greoi (pentru conditii mari) si ineficient (fiindca in cazurile de "else" practic se evalueaza si conditia si negatia). In astfel de situatii folosim predicatul built-in if/3 sau operatorul ()->();() (parantezele denota de fapt expresiile logice pe care le primeste ca parametri).

Predicatul if

Predicatul if accepta trei parametri, fiecare din cei trei parametri reprezentand un termen compus, care reprezinta o expresie logica valida formata din predicate si operatori logici. Primul parametru reprezinta conditia. In cazul in care evaluarea conditiei se termina cu succes, se evalueaza expresia din parametrul al doilea; daca, in schimb, conditia are ca rezultat no (esec) este evaluat parametrul al treilea.

Operatorul "daca...atunci..." ()->();()

Daca expresia dinainte de -> este adevarata atunci este evaluata expresia din mijloc, altfel e evaluata ultima expresie. Este important sa puneti parantezele care grupeaza expresiile de evaluat, din cauza prioritatii operatorilor. Este de asemenea important sa puneti o paranteza in jurul intregii expresii, din acelasi motiv: (()->();()). In cazul expresiilor mai complexe este o practica buna sa indentati corespunzator programul pentru a nu se genera confuzii (mai ales ca ; poate fi usor confundat cu un "sau" la o parcurgere in graba a codului).

Atentie expresia de dinainte de -> se evalueaza doar o data, adica daca se revine din backtracking la o sintagma de forma ()->();() nu se va reevalua pentru instantierea variabilelor calculate cu alte valori.

Partea de else a expresiei poate sa lipseasca, adica sa fie doar ()->(), in cazul acesta expresia e echivalenta cu ()->();fail

Exemplu:

p(1).
p(2).
p(3).
p(4).
p(5).

pred_if1:-if(
    (p(X),X>3),
    (format('~p e mai mare ca 3\n',[X])),
    (write('conditie falsa\n'))
    ),fail;true.

pred_if1a:-if(
    (p(10)),
    (format('~p e mai mare ca 3\n',[X])),
    (write('conditie falsa\n'))
    ),fail;true.

pred_if2:-((p(X),X>3)->
    format('~p e mai mare ca 3\n',[X]);
    write('conditie falsa\n')
    ),fail;true.
Observati interogarile:
| ?- pred_if1.
4 e mai mare ca 3
5 e mai mare ca 3
yes
| ?- pred_if1a.
conditie falsa
yes
| ?- pred_if2.
4 e mai mare ca 3
yes
| ?-

Observati pe exemplul de mai sus ca pred_if1 a afisat mesaje doar pentru acei X mai mari decat 3. De ce? Cum poate fi modificat predicatul pred_if1 din programul de mai sus astfel incat pentru fiecare solutie X a lui p(X) sa afiseze un mesaj daca X e mai mare ca 3 si alt mesaj daca nu.

Definirea operatorilor proprii.

Pana acum am folosit doar operatori predefiniti. Operatorii presupun doar o noua forma de scriere. O expresie ce contine un operator poate fi scrisa in mod echivalent sub forma parantezata:

| ?- Z= +(2,1).
Z = 2+1 ?
yes
| ?- 2+1= +(2,1).
yes

Acet lucru e valabil chiar si pentru operatorii care au un test asociat; cum ar fi operatorii de comparatie:

| ?- <(2,3).
yes
| ?-

Se definesc cu ajutorul predicatului op printr-o directiva de forma: :-op(precedenta,forma_operator,simbol_operator).

De exemplu:

:-op(100, xfy, +++).

Pot fi definiti operatori direct in consola. Mai jos e un exemplu care din nou pune in evidenta ca scrierea cu ajutorul operatorilor e echivalenta cu scrierea parantezata.

| ?- op(100,xfy,+++).
yes
| ?- 2+++1 = +++(2,1).
yes
| ?-

Precedenta unui operator, conform documentatiei este un numar intre 1 si 1200, care determina in cadrul unei expresii in ce ordine se vor realiza operatiile. Precedenta e inversa prioritatii, cu cat precedenta e mai mare cu atat prioritatea e mai mica. Atunci cand setati precedenta operatorilor custom este bine sa aveti in vedere precedenta operatorilor predefiniti pentru a asigura o buna functionare a programului.

Parametrul din mijloc, determina sintaxa operatorului (pozitia fata de operanzi) si conform documentatiei poate avea valorile: xfx,xfy,yfx (pentru forma infixata);fx,fy (forma prefixata);xf,yf (forma postfixata).
Litera y din acest parametru se refera la asociativitate. De exemplu, pentru operatorul definit cu op(100, xfy, +++), daca am avea o expresie de forma 1 +++ 2 +++ 3, interpretorul va considera 2 +++ 3 mai intai deoarece y-ul e in partea dreapta. Daca am fi avut in loc de xfy, yfx, atunci s-ar fi considerat 1 +++ 2 mai intai.