You are on page 1of 56

Capitolul II.

LISP
Intenia acestui capitol nu este aceea de a da o descriere exhaustiv a
limbajului ci de a introduce noiunile eseniale ale Lisp-ului, care s fac
posibil scrierea de aplicaii simple la capitolele de inteligen artificial ce
urmeaz. Nu vrem ca prezentarea s urmreasc cu necesitate un anumit
dialect de Lisp. Toate exemplele noastre vor putea ns fi rulate direct n
Allegro Common Lisp, versiunea de Common Lisp creat de Franz Lisp Co
(www.fratz.com). Common Lisp (v. [Steele, Jr., 1990]) este dialectul pe care la dezvoltat Comisia de standardizare a Lisp-ului X3J13 din cadrul ANSI
(American National Standard Institute). Datorit flexibilitii deosebite a
limbajului, care parc invit la implementri aventuroase, procesul de
standardizare a fost unul de durat, dar dup anul 1990 a fost finalizat n
varianta care este acceptat astzi de cele mai multe implementri Common
Lisp.
Lisp un limbaj proiectat special pentru problema pe care o am de
rezolvat
Dac ar fi s inventm o reclam pentru promovarea limbajului Lisp, ar
trebui, probabil, s ne exprimm astfel:
Lisp este limbajul care gzduiete, cu simplitate i elegan, un
concept de prelucrare a datelor: prelucrarea simbolic. Fr a neglija
calculul numeric, limbajele simbolice au faciliti speciale de a lucra cu
simboluri nenumerice.
Lisp este limbajul care exemplific paradigma de programare
funcional. O funcie este un obiect matematic care ntoarce o
singur valoare pentru un set de valori (numii i parametri) dai n
intrare. ntr-un limbaj funcional centrale pentru programare snt
definiia de funcie, apelul i evaluarea lui, n timp ce ntr-un limbaj pur
funcional toate construciile snt exprimate ca funcii care ntorc valori
atunci cnd snt evaluate.
Lisp este limbajul care se muleaz pe problema pe care o avei de
rezolvat. Aceast afirmaie trebuie neleas n sensul ergonomiei
excepionale pe care o ofer limbajul actului de programare.
Programarea n Lisp este, pentru un cunosctor, o plcere, un
spectacol i o invitaie la creaie. Programarea n Lisp este uoar iar
productivitatea limbajului este remarcabil. Adesea un program Lisp
este mai scurt dect unul care realizeaz acelai lucru ntr-un alt limbaj.
Lisp este limbajul care se dezvolt pe msur ce rezolvai
problema. Aceast trstur provine din utilizarea macrourilor un
element cu adevrat specific Lisp-ului. Prin macrouri, care snt
secvene de cod ce suport o execuie special, se poate da nu numai
o nou interpretare noiunii de evaluare a formelor Lisp-ului, noiune
central n limbaj, ci nsi o nou sintax, nct se pot crea n acest fel
linii de cod care nu mai seamn deloc cu sintaxa obinuit a

limbajului.
Lisp este un limbaj specializat pentru prelucrarea listelor, ceea ce
se reflect n chiar numele lui (LISt Processing). Motivul pentru care
lista, o structur de date relativ nespectaculoas, poate sta la baza
unui limbaj dedicat dezvoltrii de aplicaii ntr-un domeniu att de
pretenios precum inteligena artificial este c aceast structur este
extrem de general, o list putnd nlnui nu numai simboluri precum
numere i cuvinte ci i alte liste, oferind posibilitatea reprezentrii de o
manier uniform a unor structuri orict de complicate.
Lisp este limbajul de cas al inteligenei artificiale. Caracteristicile
ce-i confer aceast calitate snt facilitatea de a procesa cu simboluri,
lista ca structur fundamental de date i mecanismele complexe de
evaluare i utilizare a macrourilor care pot duce la construcii
procesuale sofisticate i care se comport diferit n funcie de context.
Lisp incorporeaz viziunea de programare multi-strat. Implementrile
orientate-obiect ale acestui limbaj i confer toate trsturile cunoscute.
n mod particular, programarea multi-strat nseamn construcia de
straturi de definiii funcionale, ceea ce invit la o abordare bottom-up
n rezolvarea problemelor, n aa fel nct apelurile de la un nivel
superior s incorporeze definiii de funcii i construcii de pe un nivel
inferior.

Programarea multi-strat este cu deosebire o abordare care se preteaz


dezvoltrii proiectelor mari, care necesit lucru n echip, uneori conlucrarea
a mai multe echipe.
Aa cum se sugereaz n figura 1, ntr-o abordare de jos n sus, se
pleac de la un nivel de baz pentru a se construi deasupra lui un numr de
niveluri derivate. Pe nivelul de baz se afl diferite compenente ale problemei
care trebuie rezolvat ntr-un anumit limbaj de programare. Prin utilizarea
posibilitilor oferite de limbaj se creeaz deasupra acestuia un prim nivel de
instrumente ori funcii, capabile s fac fa cel puin parial cerinelor
problemei, eventual s-l depeasc n posibiliti. Acest nivel poate fi vzut
ca un alt limbaj, superior celui aflat la baz. n acelai mod, nivelurile
superioare se creeaz deasupra celor imediat inferioare, pn la atingerea
specificaiilor proiectate.
n particular, Lisp-ul invit nu numai la crearea unor limbaje de nivel
intermediar bazate pe definiii de funcii i n care construciile s rezulte prin
manipularea apelurilor, ci inclusiv la inventarea unor sintaxe speciale care s
se integreze cerinelor nivelurile de proiectare respective.
Invers, ntr-o abordare de sus n jos, se pleac de la o proiectare
iniial a soluiei. Aceasta soluie, definit n termeni generali, face apoi
obiectul unor rafinamente succesive, pn la rezolvarea efectiv a problemei.
Dificultatea ntr-o astfel de abordare const n centrarea proiectului iniial pe
soluie fr a se scpa din vedere aspecte ce ar putea s o fac neaplicabil
pe unele cazuri speciale capetele problemei. ntr-o astfel de deplasare
nefericit nu deranjeaz acoperirea unor aspecte neavute n vedere iniial ci
neacoperirea altora care snt eseniale problemei.

cazuri neprevzute n proiect,


posibil a fi acoperite de soluie
cazuri prevzute n proiect dar
neacoperite de soluie
proiectarea de jos n sus

proiectarea de sus n jos

cazuri prevzute n proiect i


acoperite de soluie

Figura 1: Crearea programelor bottom-up fa de top-down

Funcii, maniere de definire i apel


Transparena referenial. Spunem c o funcie este transparent referenial
dac valoarea ntoars de funcie depinde numai de parametrii ei de intrare.
Astfel dac scriem n C:
function foo1(x)
{ return(x+2);
}

valoarea ntoars de funcia foo1 depinde, la orice apel al ei, numai de


valoarea intrrii x. Dac ns o transformm n:
int a=2;
int function foo2(int x)
{ return(x+a);
}

atunci valoarea ntoars de foo2 la un apel al acesteia de forma foo2(3),


de exemplu, depinde nu numai de valoarea intrrii x, 3 n cazul de fa, ci i
de a variabilei globale a (2 n cazul de fa). Spunem deci c foo1 este
transparent referenial n timp ce foo2 - nu.
Efectul lateral. Se spune c o funcie are un efect lateral dac evaluarea ei
adaug contextului i alte modificri dect strict valoarea ntoars de funcie.
Astfel, funcia foo1 definit mai sus nu are efecte laterale, n timp ce funcia
foo3 de mai jos, are:
int a;
function foo3(int x)
{ a := x+2;
return(a);
}

pentru c, n afara valorii ntoarse, ea mai provoac asignarea unei valori


variabilei globale a.

De la notaia lambda la Lisp pur


Lisp este unul dintre cele mai vechi limbaje de programare aflate nc
n larg utilizare (a aprut, n ordine, dup FORTRAN). Primele specificaii ale
limbajului au fost elaborate de John McCarthy, n 1957-58, pe baza calculului dezvoltat de Alonso Church i a unor dezvoltri precedente ce au aparinut
n principal lui Herbert Simon i Allen Newell.
n notaia lambda, ntr-o definiie de funcie se pun n eviden
parametrii formali ai funciei i corpul ei:
(x) = x+2;

sau:
(x,y) = x+y;

ntr-o astfel de notaie funciile nu au nume. Asocierea unui nume unei funcii,
n vederea crerii posibilitii de apel al lor, trebuie fcut explicit:
function f: (x) = x+2;
function g: (x,y) = x+y;

ntr-un apel de funcie trebuie s apar numele ei urmat de o list de


parametri actuali:
f(3)
g(5,1)

Un apel poate, n anumite condiii, s amorseze un proces de evaluare


care s duc la generarea unui rezultat. n evaluare se disting dou faze:
prima n care parametrii formali ai funciei snt legai la valorile
corespunztoare ale parametrilor actuali din corpul definiiei funciei i a doua
n care are loc evaluarea expresiei astfel obinute a corpului funciei. n
evaluarea corpului pot s intervin alte apeluri de funcie, care se deruleaz
n aceeai manier. Astfel se pot apela funcii n funcii.
Spunem c un apel poate, n anumite condiii, porni un proces de
evaluare, pentru c o scriere f(3) nu garanteaz efectuarea evalurii. Ea nu
este dect o simpl notaie pn ce se comand explicit unui evaluator (uman
sau main) nceperea unui astfel de proces asupra unei expresii n care
apare notaia de apel:
g(f(3),1) g(5,1) 6

unde trebuie citit se evalueaz la.


ntre caracteristicile eseniale care definesc aa-numitul Lisp pur se
numr faptul c funciile snt transparente referenial, c funciile nu au
efecte laterale i c din limbaj lipsesc asignrile.

Evaluarea numeric fa de evaluarea simbolic.


Fie funcia f definit mai sus. Apelat asupra parametrului 3 avem o
evaluare numeric:
f(3) 3 + 2 5

Dac ns parametrul actual care ia locul parametrului formal x din corpul


definiiei funciei este unul nenumeric, s zicem a, atunci avem de a face cu o
evaluare simbolic:
f(a) a + 2

pentru c forma rezultat conine un simbol nenumeric.


Beneficiile unui Lisp impur: Common Lisp
Pe considerente de cretere a puterii de expresie a limbajului i de
mrire a productivitii activitii de programare, Lisp-ul pur a fost ulterior
impurificat n absolut toate direciile posibile, fr ns a pierde, prin aceasta,
sclipirea de geniu original. Astfel, dup cum bine vom constata n cele ce
urmeaz, s-au acceptat definiii de funcii care s nu mai respecte trstura
de transparen referenial (fr a le face ns astfel opace referenial...), sau ncurajat efectele laterale, s-au adoptat cteva operaii de asignare, ba
chiar s-a permis ca o funcie s ntoarc mai mult dect o unic valoare,
aceast din urm trdare fcndu-se ns, aparent paradoxal, fr s se
ncalce ctui de puin definiia matematic a funciei (o coresponden de la
un domeniu de intrare la un domeniu de ieire n care oricrei valori de intrare
i corespunde o singur valoare de ieire).
Tipuri de date n Lisp
Schema urmtoare face o clasificare a celor mai uzuale tipuri de date
n Lisp:
s-expresie (expresie simbolic symbolic expression)
atom
numeric
ntreg
raional (fracie)
real
complex
nenumeric (n fapt atom literal, noi i vom numi atom simbolic
sau, simplu, simbol)
list
list simpl
list cu punct
ir (array)

ir de caractere
tablou (cu oricte dimensiuni)
tabel de asociaie (hash table) structur de date care permite asocierea i
regsirea cu uurin a valorilor ataate simbolurilor;
pachet (sau spaiu de nume) colecii ncapsulate de simboluri. Un parser
Lisp recunoate un nume prin cutarea lui n pachetul curent;
flux de date surs de date, tipic ir de caractere, utilizat pentru canalizarea
operaiilor de intrare/ieire.
Numai datele au tipuri. O variabil n Lisp nu are definit un tip. Ea poate primi
ca valoare orice tip de dat.
Construciile limbajului
n Lisp operm cu urmtoarele categorii de obiecte: variabile,
constante, date, funcii, macro-uri i forme speciale (la acestea mai trebuie
adugate clasele i metodele n implementrile orientate-obiect).
Variabilele snt asociaii ntre simboluri utilizate n anumite spaii
lexicale ale limbajului i valori asociate acestora. Dup cum vom vedea mai
departe, exist trei moduri prin care o variabil poate s primeasc valori,
uneori chiar mai multe odat: asignarea, legarea i prin intermediul listelor de
proprieti. Dou dintre aceste moduri de a primi valori (asignarea i listele de
proprieti) snt caracteristice oricrui simbol Lisp. Ceea ce difereniez un
simbol lexical de o variabil snt numai situaiile n care variabilele pot fi legate
la valori.
Constantele snt simboluri (n general ale sistemului) care au ataate
valori ce nu pot fi modificate.
n Lisp nu exist proceduri ci numai funcii, n sensul c orice rutin
ntoarce obligatoriu i o valoare. Macro-urile snt funcii care au un mecanism
de evaluare special, n doi pai: expandarea i evaluarea propriu-zis
(prezentate n seciunea Macro-uri. Definiie, macroexpandare i evaluare).
Formele speciale snt funcii de sistem care au, n general, o sintax i un
comportament aparte.
Un pic de sintax
Urmtoarele caractere au o semnificaie special n Lisp:
( o parantez stng marcheaz nceputul unei liste;
) o parantez dreapt marcheaz sfritul unei liste;
un apostrof, urmat de o expresie e, e, reprezint o scriere condensat
pentru un apel (quote e);
; punct-virgula marcheaz nceputul unui comentariu. El nsui mpreun cu
toate caracterele care urmeaz pn la sfritul rndului snt ignorate;
ntre o pereche de ghilimele se include un ir de caractere;
\ o bar oblic stnga prefixeaz un caracter pe care dorim s-l utilizm n
contextul respectiv ca o liter i nu cu semnificaia lui uzual. De exemplu,

irul de caractere a\B este format din trei caractere: a, i B pentru c


cel de al doilea rnd de ghilimele nu trebuie considerate ca nchiznd irul ci ca
un simplu caracter din interiorul lui;
| ntre o pereche de bare verticale poate fi dat un ir de caractere speciale
pe care dorim s le utilizm ntr-un nume de simbol. Inclusiv caracterul bar
oblic stnga (\) poate apare ntre o pereche de bare verticale, dar ea i
pstreaz semnificaia de prefix. n particular, ea poate prefixa o bar
vertical ntre o pereche de bare verticale. Astfel, irurile:
|&.+\:;|
a|&.+\|:;|b
a|&.+\| 2)<nl>|b

unde prin <nl> am notat caracterul rnd-nou (new line), pot constitui nume de
simboluri;
# un diez semnaleaz c urmtorul caracter definete modul n care trebuie
interpretat construcia care urmeaz. Cea mai important utilizare a diezului
este de a semnala o form funcional, ntr-o secven de genul: #fn, unde
fn este un nume sau o lambda expresie (definiie de funcie fr nume);
` un accent invers semnaleaz c ceea ce urmeaz este un template care
conine virgule (mai multe despre template-uri, n seciunea Despre
apostroful-stnga). Un template funcioneaz ca un program care modific
forma unui ir de obiecte Lisp;
, virgulele snt utilizate n interiorul template-urilor pentru a semnala cazuri
speciale de nlocuiri;
: dou puncte semnaleaz, n general, c urmtorul simbol trebuie privit ca
un simbol constant care se evalueaz la el nsui. n alte cazuri, dou puncte
despart numele unui pachet de numele unei variabile definite n acel pachet
(de exemplu, n user1:alpha, user1 este numele unui pachet, iar alpha
este numele unei variabile).
Implementrile uzuale de Common Lisp snt insensibile la forma
caracterelor (minuscule sau majuscule). Intern ns, formele Lisp snt
reprezentate cu majuscule, de aceea formele rezultate n urma evalurilor snt
redate n astfel de caractere.
Numerele ntregi zecimale pot fi precedate opional de un semn i
urmate opional de un punct zecimal (adugarea unui zero dup punct le
transform ns n numele reale). Numere ntregi n alte baze au sintaxa
#nnRddddd sau #nnrddddd, n care nn exprim baza iar ddddd numrul.
Bazele 2, 8 i 16 permit i o alt scriere, respectiv #b pentru #2r, #o pentru
#8r, i #x pentru #16r.
Fraciile sau numerele raionale snt reprezentate ca un raport dintre
un numrtor i un numitor, adic o secven: semn (opional), ntreg
(numrtor), caracterul /, ntreg (numitor). Reprezentarea intern i cea
tiprit este ntotdeauna a unei fracii simplificate. Dac numrtorul e notat n
alt baz dect cea zecimal, numitorul va fi interpretat n aceeai baz. O
fracie simplificabil la un ntreg este convertit automat la ntreg. Numitorul
trebuie s fie diferit de zero. Exemple de fracii:
1/2
-2/3

4/6 (echivalent cu fracia 2/3)


#o243/13 (echivalent cu 163/11)

Numerele reale au notaia obinuit ce cuprinde punctul zecimal,


semnul (opional) i puterea (opional). Formatul simplu sau dublu poate, de
asemenea fi specificat:
3.1415926535897932384d0 ; o aproximare n format dublu pentru
6.02E+23
; numrul lui Avogadro

Numerele complexe snt notate ca o secven: #c(<real>,


<imaginar>), n care <real> i <imaginar> snt numere n acelai
format. Exemple de numere complexe:
#c(1 2)
#c(0 1)
#c(2/3 1.3)

; numrul 1 + 2*i
; numrul i
; convertit intern la #c(0.6666667 1.3)

Orice combinaie de litere i cifre poate constitui un nume de simbol.


n plus, oricare dintre urmtoarele caractere poate interveni ntr-un nume de
simbol: + - * / @ $ % ^ & _ = < > ~ .1 Printre altele,
aadar, urmtoarele iruri pot fi nume de simboluri:
alpha
a21
1a2
*alpha* (variabilele globale sau de sistem au, n general, nume care ncep i se
ncheie cu asterisc)
a+b
a-b+c
a/1
$13
1%2
a^b
cel_mare
a=b
a<b~c&
dudu@infoiasi

Liste i reprezentarea lor prin celule cons


n Lisp o list se noteaz ca un ir de s-expresii separate prin blancuri
i nchise ntre paranteze. De exemplu:
() lista vid, notat i nil sau NIL;
(a alpha 3) lista format din 3 atomi, dintre care primii doi nenumerici, iar al
treilea numeric;
(alpha (a) beta (b 1) gamma (c 1 2)) lista format din 6 elemente: primul,
al treilea i al cincilea fiind atomi nenumerici, iar al doilea, al patrulea i al aselea fiind
la rndul lor liste cu unu, dou, respectiv trei elemente.

Ultimul caracter din acest rnd, punctul (.), este caracter de sfrit de propoziie i, deci, nu
trebuie considerat printre caracterele enunate.

Tradiional, listelor li se pot asocia notaii grafice care i au obria n


primele implementri ale Lisp-ului. Astfel, o list se noteaz ca o celul
(numit celul cons) ce conine dou jumti, prima coninnd un pointer
ctre primul element al listei, iar a doua unul ctre restul elementelor listei.
Notaia grafic a listei ca celul cons (v. figura 2) mimeaz definiia recursiv
a structurii de list: o list este format dintr-un prim element i lista format
din restul elementelor. S observm c aceast definiie se poate aplica
pentru liste nevide, adic liste care au cel puin un prim element. Tot
tradiional, prima jumtate a unei celule cons, se numete car, iar cea de a
doua cdr:
cdr

car
car

cdr
pointer ctre restul
listei (a b c)

pointer ctre
primul element al
listei (a b c)
a

pointer ctre
primul element
al listei (c)
c

pointer ctre
primul element
al listei (b c)
b

ntreaga list (a b c)
e reprezentat de
aceast celul cons

restul listei (c) e


lista vid

pointer ctre restul


listei (b c)

lista (c) e
reprezentat de
aceast celul cons

lista (b c) e
reprezentat de
aceast celul cons

Figura 2: Notaia grafic de list

Evident, prin construcii de acest fel pot fi reprezentate liste n liste, pe


oricte niveluri. Figura 3 exemplific lista:
(a (alpha beta) (b (beta (gamma delta)) c))

a
alpha

beta

c
beta

gamma

delta

Figura 3: Un exemplu de list

Perechi i liste cu punct. Celula cons n care att car-ul ct i cdr-ul


pointeaz ctre s-expresii atomice poart numele de pereche cu punct, pentru
c n notaia liniar cele dou elemente snt reprezentate ntre paranteze,
separate prin punct. Astfel celula cons din Figura 4a. se noteaz: (a.b).
Dac o list conine perechi cu punct o vom numi list cu punct.
Reprezentarea din Figura 4b. trebuie notat (a b.c), pentru c restul listei

reprezentat de prima celul cons este lista cu punct (b.c). S mai


observm c orice list simpl poate fi notat i ca o list cu punct. Astfel, lista
(a b c) poate fi notat (a.(b.(c.nil))). Nu este adevrat ns c orice
list cu punct poate fi notat ca o list simpl.

a.

b c

b.

Figura 4: Liste cu perechi cu punct

Evaluarea expresiilor Lisp


Un program Lisp este format numai din apeluri de funcii. Practic, nsi
o definiie de funcie este, de fapt, tot un apel al unei funcii care creeaz o
asociere ntre un nume al funciei, o list de parametri formali i un corp al ei.
Vom presupune n cele ce urmeaz c expresiile Lisp snt interpretate
ntr-o bucl READ-EVAL-PRINT n care are loc o faz de citire a expresiei de
pe fluxul de intrare, urmat de o faz de evaluare a ei i de una de tiprire a
rezultatului. Vom presupune c prompter-ul Lisp este >. Orice expresie ce
urmeaz a fi evaluat se prezint pe un rnd imediat dup prompter, pentru ca
pe rndul urmtor s apar rezultatul tiprit al evalurii.
Expresiile se evalueaz dup cteva reguli simple:
- un atom numeric se evalueaz la el nsui:
> 3
3
> 3.14
3.14

n Lisp pot fi reprezentate numere orict de mari fr riscul de a provoca


vreo depire:
> 9999999999999999999999999999999999999999999999999999999
9999999999999999999999999999999999999999999999999999999

un atom simbolic care are o valoare predefinit (n sensul asignrii sau


al legrii) se evalueaz la aceast valoare. Astfel, presupunnd c
simbolului a i se asociase anterior valoarea alpha:
> a
alpha

Excepie fac simbolurile: nil care, fiind notaia pentru lista vid ct i
pentru valoarea logic fals, se evalueaz la el nsui i t care se
evalueaz la valoare logic adevrat (true sau TRUE). Orice sexpresie diferit de nil este considerat a fi echivalent logic cu
true:

> nil
NIL
> t
TRUE

un atom simbolic care nu are o valoare predefinit, tentat a fi evaluat,


va provoca un mesaj de eroare de genul UNBOUND-VARIABLE;
o expresie simbolic prefixat cu apostrof (quote) se evalueaz la ea
nsi:
> alpha
alpha
> 3
3
> (a 3 ())
(a 3 NIL)

o list care are pe prima poziie un atom recunoscut ca nume de form


Lisp predefinit sau de funcie definit de utilizator se evalueaz astfel:
forma Lisp sau funcia dicteaz maniera n care se face evaluarea
urmtoarelor elemente ale listei: care dintre acestea se evalueaz i
care snt lsate neevaluate. Evaluate ori nu, argumentele apelului snt
trecute apoi corpului formei sau al funciei, drept parametri actuali.
Valoarea rezultat n urma evalurii corpului formei sau funciei cu
aceti parametri este cea care se ntoarce:
> (+ 2 1)
3
Primul element al listei este recunoscut drept numele funciei de adunare, +.
Parametrii adunrii rezult prin evaluarea urmtoarelor dou elemente ale listei,
respectiv atomii numerici 2 i 1. Cum evaluarea i las neschimbai, ei snt trecui ca
atare drept parametri actuali ai adunrii. Aplicarea funciei + asupra lor produce
rezultatul 3, care este tiprit. n Lisp, operatorii de calcul aritmetic, ca i orice alt nume
de funcie, se noteaz naintea argumentelor (notaie prefixat).
> (setq a alpha)
alpha
Primul element al listei este recunoscut drept forma Lisp setq. Forma setq
asigneaz argumentelor din apel de pe poziiile impare valorile rezultate din
evaluarea argumentelor din apel de pe poziiile pare. Astfel simbolului a i se
asigneaz rezultatul evalurii lui alpha adic alpha. Valoarea ultimului element al
listei este i valoarea ntoars de funcie. Forma setq constituie un exemplu de
funcie cu efect lateral, efect ce se manifest prin lsarea n context a unor valori
asignate unor simboluri nenumerice. Acesta este un caz n care rezultatul scontat
principal este cel dat de efectul lateral i nu de valoarea ntoars de form.

evaluarea unei liste care are pe prima poziie o lambda-expresie va fi


prezentat n seciunea Lambda expresii;
o list care are pe prima poziie un nume de macrodefiniie va fi
prezentat n seciunea Macro-uri. Definiie, macroexpandare i
evaluare;
orice altceva (ca de exemplu, o list care are pe prima poziie o list
diferit de o lambda-expresie, sau un atom ce nu este cunoscut ca

nume de form Lisp sau macro, aadar nu face parte din biblioteca de
funcii ale Lisp-ului i nici nu este o funcie definit de utilizator)
provoac, n momentul lansrii n evaluare, un mesaj de eroare de
genul UNDEFINED-FUNCTION.

Funcii i forme Lisp


Limbajul Lisp numr o clas semnificativ de funcii predefinite.
Funciile Lisp-ului i evalueaz toate argumentele nainte de a le transfera
corpului funciei. n afara funciilor, prin utilizarea macro-urilor (v. seciunea
Macro-uri. Definiie, macroexpandare i evaluare), s-au creat forme speciale
care aplic reguli specifice de evaluare a argumentelor.
n aceast seciune vor fi prezentate cteva funcii i forme ale Lisp-ului,
strictul necesar pentru a putea aborda cu ele probleme din domeniul
inteligenei artificiale. n prezentarea acestor funcii vom adopta o convenie
de notaie, care s ne permit s identificm argumentele ce se evalueaz
fa de cele care nu se evalueaz, tipul acestora, rezultatul evalurii, precum
i manifestarea, atunci cnd va fi cazul, a efectelor laterale. Aceste convenii
de notare snt urmtoarele:
e s-expresie
n atom numeric
i numr ntreg
c numr complex
s atom nenumeric (simbolic)
l list
f funcional.
Dac un astfel de simbol, aflat pe poziia unui argument al unui apel de
form Lisp, este prefixat cu un apostrof (e, spre exemplu), vom nelege c
argumentul aflat pe acea poziie se evalueaz la un obiect Lisp de tipul indicat
de simbol (s-expresie, n cazul exemplului de mai sus). Dimpotriv, dac el
este notat fr apostrof, atunci argumentul respectiv, ce trebuie s respecte
tipul indicat de simbol, nu se evalueaz (s observm c aceast convenie
este una mnemonic pentru c, ntr-adevr, prin evaluarea unui obiect Lisp
prefixat cu un apostrof, rmnem cu argumentul fr prefix). n plus, pentru
simplificare, vom nota ntotdeauna argumentele ce rezult n urma evalurii
obiectelor prefixate cu apostrof, prin aceleai litere. Astfel, dac n1, nk snt
argumentele evaluabile i de tip numeric ale unui apel, vom considera, fr a
mai meniona acest lucru, c numerele ce rezult n urma evalurii acestora
snt notate cu n1, nk. De asemenea, vom considera de la sine neles c
orice apel realizat cu argumente de tipuri diferite celor indicate n definiii duce
la generarea unor mesaje de eroare.
Ca i mai sus, vom utiliza simbolul cu semnificaia se evalueaz la.
Astfel, n definiiile de funcii care urmeaz, printr-o notaie e1 e2 vom
nelege c forma Lisp e1 (o s-expresie, n acest caz) se evalueaz la forma
e2 (de asemenea o s-expresie). Vom renuna ns la descrierea formal a
valorii ntoarse n favoarea unei descrieri libere, atunci cnd, datorit

complexitii operaiilor efectuate de funcie, o descriere formal ar ncepe s


semene mult prea mult cu nsi o implementare a ei.
Renunarea la restriciile unui Lisp pur a dus inclusiv la acceptarea unui
mecanism de evaluare care s ntoarc mai mult dect o singur valoare.
Astfel, construcia values din Common Lisp pune ntr-o structur special
valori multiple (vom vedea n seciunea Valori multiple i exploatarea lor) cum
pot exploatate acestea). n notaia noastr vom separa prin virgule valorile
multiple rezultate dintr-o evaluare: e1 e2,, en. n plus, vom nota prin
~nil o valoare despre care ne intereseaz s tim c e diferit de nil.
n cazul n care evaluarea produce i efecte laterale, le vom nota dup
o bar vertical: e1 e2 | <efecte>. Un anumit tip de efect lateral,
atribuirea unei valori unui simbol, va fi notat printr-o sgeat stng: se, cu
semnificaia: simbolul s se leag la valoarea e, sau simbolului s i se
asigneaz valoarea e. De asemenea, n notarea efectelor laterale, vom atribui
virgulei (,) semnificaia unui operator de execuie serial i dublei linii
verticale (), cea de operator de execuie paralel. Cu alte cuvinte, o
secven de efecte laterale E1,, Ek va semnifica consumarea secvenial,
n ordinea stnga-dreapta, a acestora, pe cnd printr-o notaie E1... Ek vom
nelege consumarea acelorai efecte n paralel.
Atunci cnd este important momentul evalurii formei care produce
valoarea ntoars de apelul de funcie relativ la momentele producerii
efectelor laterale, vom nota evaluarea formei care produce valoarea ntoars
printre efectele laterale ntr-o pereche de paranteze ptrate, de exemplu: [e].
Dac anumite efecte laterale, produse pe parcursul evalurii unei
forme, nu snt persistente, n sensul c nu rmn ca observabile n contextul
de evaluare a formei i dup terminarea evalurii formei, atunci ndeprtarea
lor este notat explicit. Astfel, ndeprtarea efectului asignrii unei valori
simbolului s va fi notat unbind(s). Pentru a descrie aciuni repetitive vom
folosi structura de iterare while <test> {<corp>} intercalat n lista
efectelor laterale.
n prezentrile apelurilor de funcii pot s apar i cuvinte cheie, care
se recunosc pentru c snt ntotdeauna prefixate cu caracterul dou-puncte
(:). ntr-un apel real ele trebuie reproduse ca atare. Astfel de cuvinte cheie,
pot fi:
:test cu semnificaia c ceea ce urmeaz este un test care va
aciona prin valoarea T; de obicei el prefixeaz un nume de funcional,
care este dat n sintaxa #f;
:test-not prefixeaz un test care acioneaz prin valoarea NIL.
Asignarea unei valori unui simbol
ntr-un limbaj imperativ, o notaie de genul x:=y, unde x i y snt
cunoscute ca fiind variabile, are interpretarea uzual: se atribuie variabilei x
valoarea pe care o are variabila y. S observm ns c, n sine, o astfel de
notaie las loc la cel puin nc dou interpretri: variabila x se leag la
variabila y, nelegnd prin aceasta c nct ori de cte ori y se va modifica, x
se va modifica n acelai mod i variabilei x i se atribuie ca valoare nsui
simbolul y. Dup cum vom vedea n seciunea Variabile i valorile lor, Lisp-ul

face posibile toate aceste interpretri. Cea mai apropiat de accepiunea din
limbajele imperative, atribuirea, sau asignarea, unei valori este prezentat
n continuare.
Despre forma setq s-a vorbit deja informal n seciunea Evaluarea
expresiilor Lisp. Evaluarea argumentelor de pe poziiile pare i, respectiv,
asignrile se fac n ordine, de la stnga la dreapta:
(setq s1 e1 sk ek) ek|s1e1,, sk[ek]
> (setq x (a b c) y (cdr x))
(B C)

Forma set este ca i setq numai c ea i evalueaz toate


argumentele: dac e1 s1, e2*k-1 s2*k-1 atunci:
(set e1 e2 e2*k-1 e2*k) e2*k|s1e2, s2*k-1[e2*k]
> (set (car (x y)) (a b c) (car x) (cdr x))
(B C)
> a
(B C)

Forma psetq este analog lui setq cu deosebirea c asignrile se fac n


paralel: Mai nti toate formele de rang par snt evaluate serial i apoi tuturor
variabilelor (simbolurile de pe poziiile impare) le snt asignate valorile
corespunztoare: dac e1 s1, e2*k-1 s2*k-1 atunci:
(psetq e1 e2 e2*k-1 e2*k) e2*k|e2,, [e2*k], s1e2
s2*k-1e2*k
n exemplul urmtor valorile lui x i y snt schimbate ntre ele:
> (setq x a)
A
> (setq y b)
B
> (psetq x y y x)
NIL
> x
B
> y
A

Funcii pentru controlul evalurii


Am vzut deja c prefixarea unei s-expresii cu un apostrof este
echivalent apelului unei funcii quote. Aadar quote mpiedic evaluarea:
(quote e) e
Funcia eval foreaz nc un nivel de evaluare. Astfel, dac: e1
e2
i e2 e3 , atunci:
(eval e1) e3
> (setq x 'a a 'alpha)
ALPHA
> (eval x)

ALPHA
> (eval (+ 1 2))
3

Operaii asupra listelor


Construcia listelor
Funcia cons construiete o celul din dou s-expresii, rezultate din
evaluarea celor dou argumente, punndu-l pe primul n jumtatea car i pe
cel de al doilea n jumtatea cdr:
(cons e1 e2) (e1.e2)
> (cons a nil)
(A)
> (cons a b)
(A . B)
> (cons a (a b))
(A A B)
> (cons alpha (cons beta nil))
(ALPHA BETA)

Funcia list creeaz o list din argumentele sale evaluate:


(list e1 ek) (e1.((ek.nil)))
> (list a b c)
(A B C)
> (list a (b c))
(A (B C))

Funcia append creeaz o list prin copierea i punerea cap la cap a


listelor obinute din evaluarea argumentelor sale. Astfel, dac:
l1 (e11.((e1k1.nil))),, ln (en1.((enkn.nil)))
atunci:
(append l1 ln) (e11.((e1k1.((en1.((enkn.nil)))))))
> (append (a b c) (alpha beta) (1 gamma 2))
(A B C ALPHA BETA 1 GAMMA 2)

De notat c ultimul argument poate fi orice s-expresie, iar nu o list cu


necesitate.
> (setq l1 (list 'a 'b) l2 (list 'c 'd 'e) l3 'f)
F
> (append l1 l2 l3)
(A B C D E . F)

Funcia creeaz celule cons noi corespunztoare tuturor elementelor listelor


componente, cu excepia celor aparinnd ultimei liste, pe care le utilizeaz ca
atare.
Accesul la elementele unei liste
Funciile car i cdr extrag s-expresiile aflate n poziiile car, respectiv
cdr, ale unei celule cons. Dac lista e vid car ntoarce nc nil. Dac l
(e1.e2), atunci:

(car
(car
(cdr
(cdr

l) e1
nil) nil
l) e2
nil) nil
> (car
A
> (car
(ALPHA
> (cdr
(B C)
> (cdr
NIL
> (cdr
B

(a b c))
((alpha beta) b c))
BETA)
(a b c))
(a))
(a . b))

Prin combinaii car-cdr poate fi accesat orice element de pe orice


nivel al unei liste. Pentru simplificarea scrierii, implementrile de Lisp pun la
dispoziie notaii condensate, ca de exemplu caddar n loc de (car (cdr
(cdr (car ...))))).
Funcia nth selecteaz un element de pe o poziie precizat a unei
liste. Primul argument trebuie s fie un ntreg nenegativ, iar al doilea o list.
Dac l (e0.((em.nil))) i 0nm, atunci:
(nth n l) en
Dac nm, atunci:
(nth n l) nil
> (nth 1 (a b c))
B
> (nth 3 (a b c))
NIL

Similar, funcia nthcdr efectueaz de un numr ntreg i pozitiv de ori


cdr asupra unei liste. Dac l=(e0.((em.nil))) i 0nm, atunci:
(nth n l) (en.((em.nil)))
Dac nm, atunci:
(nth n l) nil
> (nthcdr 0 (a b c))
(A B C)
> (nthcdr 1 (a b c))
(B C)
> (nthcdr 4 (a b c))
NIL

Funcia last ntoarce ultima celul cons a unei liste. Dac l=(e1.(
(ek.nil))), atunci:
(last l) (ek.nil)
(last nil) nil
> (last (a b c))
(c)

> (last (cons a b))


(A . B)
> (last (cons a nil))
(A)

Operaii cu numere
Lisp-ul are o bibliotec foarte bogat de funcii aritmetice. Dintre ele,
prezentm doar cteva n rndurile urmtoare.
Operaiile de adunare, scdere, nmulire i mprire (k 1):
(+ n1 nk) n1 + + nk
(- n) -n i dac k > 1, atunci: (- n1 nk) n1 - - nk
(* n1 nk) n1 * * nk
(/ n1 nk) n1 / / nk
> (+
6
> (-3
> (*
6
> (/
1

1 2 3)
5 (+ 3 3) 2)
1 2 3)
6 3 2)

Funcia / ntoarce o fracie (numr raional) dac argumente snt


numere ntregi i mprirea nu e exact.
> (/ 2 3)
2/3

Aceast funcie, ca i tipul de dat numr raional, ofer, aadar, o cale foarte
elegant de a opera cu fracii zecimale:
> (+ 1 (/ 2 3))
5/3
> (* (/ 2 3) (/ 3 2))
1
> (+ (/ 1 3) (/ 2 5))
11/15

Aa cum am artat i mai sus, nu exist limite n reprezentarea


numerelor, de aceea apeluri ca cele de mai jos snt posibile:
> (* 99999999999999999999999999999999999999999999999999999999
8888888888888888888888888888888888888888888888888888)
888888888888888888888888888888888888888888888888888799991111111
111111111111111111111111111111111111111111112

Pentru toate funciile aritmetice funcioneaz urmtoarea regul de


contaminare: n cazul n care argumentele unei funcii numerice nu snt de
acelai tip, toate snt aduse la tipul celui mai puternic, conform urmtoarei
ierarhii: complex > real > raional > ntreg.

Reguli de canonizare: un raional cu numitor 1 e considerat ntreg, un


real cu 0 dup virgul e considerat ntreg, un complex cu partea imaginar 0 e
considerat real.
> (+ (/ 1 3) (/ 2.0 5))
0.73333335

Toate operaiile de mai sus se aplic i asupra argumentelor numere


complexe:
> (+ #c(1 2) #c(3
#c(4 4)
> (- #c(1 2) #c(3
-2
> (* #c(1 2) #c(3
#c(-1 8)
> (/ #c(1 2) #c(3
#c(7/13 4/13)

2))
2))
2))
2))

Exist mai multe funcii de rotunjire a unei valori. Funciile floor i


ceiling rotunjesc o valoare n jos, respectiv n sus. Dac n este ntreg, real
sau zecimal i mn<m+1, cu m un ntreg, atunci:
(floor n) m, n - m
Dac n este ntreg, real sau zecimal i m<nm+1, cu m un ntreg, atunci:
(ceiling n) m + 1, n (m + 1)
> (floor 2)
2
0
> (floor 2.9)
2
0.9000001
> (floor 2/3)
0
2/3
> (ceiling 2)
2
0
> (ceiling 2.9)
3
-0.099999905
> (ceiling 2/3)
1
-1/3

Dac apelurile se efectueaz cu dou argumente ntregi se obine


calculul ctului i al restului (comportamentul unei funcii care calculeaz
modulo). Astfel, dac c este cel mai mic ntreg pozitiv i r este un ntreg
pozitiv astfel nct n1 = c * n2 + r, atunci:
(floor n1 n2) c, r
>(floor 7 3)
2
1

Dac c este cel mai mic ntreg pozitiv i r este un ntreg pozitiv, astfel
nct n1 = (c+1) * n2 - r, atunci:
(ceiling n1 n2) c + 1, -r
>(ceiling 7 3)
3
-2

Forme Lisp de incrementare/decrementare: funciile 1+, respectiv 1-:


(1+ n) n + 1
(1- n) n - 1
>
1
>
2
>
1
>
0
>
1

(setq x 1)
(1+ x)
x
(1- x)
x

Dup cum se observ, funciile nu afecteaz argumentele. Macro-urile incf


i decf fac acelai lucru, dar afecteaz argumentele:
(incf n) n + 1 | n n + 1
(decf n) n - 1 | n n - 1
>
1
>
2
>
2
>
1
>
1

(setq x 1)
(incf x)
x
(decf x)
x

Dac n apel apare i un al doilea argument, el e considerat incrementul,


respectiv decrementul:
(incf n1 n2) n1 + n2 | n1 n1 + n2
(decf n1 n2) n1 - n2 | n1 n1 - n2
> (setq x 1)
1
> (incf x 2)
3
> x
3
> (decf x 4)
-1
> x
-1

De notat c argumentele funciilor 1+, 1-, incf i decf pot fi orice fel de
numr, inclusiv complex. n acest din urm caz incrementarea/decrementarea
se aplic numai prii reale.
Calculul valorii absolute: dac n0, atunci:
(abs n) n
Dac n<0, atunci:
(abs n) -n
> (abs (- 3 5))
2

Dac argumentul este numr complex, rezultatul este modulul, considerat ca


numr real.
> (abs #c(3 -4))
5.0

Calculul maximumului i minimumului unui ir de numere. Dac


n=max(n1, nk), atunci:
(max n1 nk) n
Dac n=min(n1, nk), atunci:
(min n1 nk) n
Aritmetica numerelor complexe
n afara operaiilor uzuale cu numere, i care se rsfrng i asupra
numerelor complexe (*, -, *, /, 1+, 1-, incf, decf, abs), urmtoarele funcii
accept ca argumente numai numere complexe: conjugate, realpart,
imagpart. Dac c #c(n1 n2), atunci:
(conjugate c) #c(n1 -n2)
Dac c #c(n1 n2) , atunci:
(realpart c) n1
Dac c #c(n1 n2) , atunci:
(imagpart c) n2
Funcia complex compune un numr complex dintr-o parte real i
una imaginar:
(complex n1 n2) #c(n1 n2)
> (complex 1 2)
#c(1 2)

Predicate
Predicatele snt funcii care ntorc valori de adevr.

Predicate care verific tipuri i relaii


Dac e este un atom, atunci:
(atom e) t
altfel:
(atom e) nil
Dac e este un atom simbolic, atunci:
(symbolp e) t
altfel:
(symbolp e) nil
Dac e este numr, atunci:
(numberp e) t
altfel:
(numberp e) nil
Dac e este numr ntreg, atunci:
(integerp e) t
altfel:
(integerp e) nil
Dac e este numr real, atunci:
(floatp e) t
altfel:
(floatp e) nil
Dac n este numr ntreg par, atunci:
(evenp n) t
altfel:
(evenp n) nil
Dac n este numr ntreg impar, atunci:
(oddp n) t
altfel:
(oddp n) nil
Dac n este numr pozitiv, atunci:
(plusp n) t
altfel:
(plusp n) nil
Dac n este numr negativ, atunci:
(minusp n) t
altfel:
(minusp n) nil
Dac n=0, atunci:

(zerop n) t
altfel:
(zerop n) nil
Dac n1= =nk, atunci:
(= n1 nk) t
altfel:
(= n1 nk) nil
Dac n1< <nk, atunci:
(< n1 nk) t
altfel:
(< n1 nk) nil
Dac n1> >nk, atunci:
(> n1 nk) t
altfel:
(> n1 nk) nil
Dac n1 nk, atunci:
(<= n1 nk) t
altfel:
(<= n1 nk) nil
Dac n1 nk, atunci:
(>= n1 nk) t
altfel:
(>= n1 nk) nil
Dac e s i s este un simbol legat, atunci:
(boundp e) t
altfel:
(boundp e) nil
> (setq x
Z
> (boundp
T
> (boundp
T
> (boundp
T
> (boundp
NIL

y y z)
x)
x)
y)
y)

Dac e1 i e2 snt izomorfe structural (dei pot fi obiecte Lisp distincte)


atunci:
(equal e1 e2) t
altfel:
(equal e1 e2) nil

> (equal alpha alpha)


T
> (equal (a b c) (cons a (cons b (cons c nil))))
T
> (equal (a b c) (cons a (cons b c)))
NIL
> (setq x (a b c) y (cdr x))
(B C)
>(equal y (b c))
T
>(equal (cdr x) y)
T

Dac e1 i e2 reprezint aceeai structur (obiect) Lisp, cu alte cuvinte,


dac ele adreseaz elemente aflate la aceeai adres n memorie, atunci:
(eq e1 e2) t
altfel:
(eq e1 e2) nil
> (eq alpha alpha)
T

n Common Lisp simbolurile snt reprezentate cu unicitate. Din acest


motiv apelul de mai sus se evalueaz la T. Standardul nu oblig ns
reprezentarea cu unicitate a numerelor i nici a irurilor de caractere. Din
acest motiv nu poate fi garantat un rezultat pozitiv n cazul testrii cu eq a
dou numere egale sau a dou iruri de caractere identice i, ca urmare,
astfel de operaii trebuie evitate:
> (eq 1 1)
???
> (eq "alpha" "alpha")
???
> (eq (a b c) (cons a (cons b (cons c nil))))
NIL
> (setq x (a b c) y (cdr x))
(B C)
> (eq (cdr x) (b c))
NIL
> (eq (cdr x) y)
T

Predicatul eql funcioneaz la fel ca i eq, cu excepia faptului c el


ntoarce T inclusiv pentru numere egale, deci obiecte care snt i conceptual,
iar nu numai strict implementaional, identice. n privina listelor i a irurilor de
caractere eq i eql se comport la fel.
> (eql 1 1)
T

Dac e=nil atunci:


(null e) t
altfel:
(null e) nil

Liste privite ca mulimi


Funcia member verific existena unei s-expresii ntr-o list. Dac
l (e1.((ek.((en.nil))))), unde ek este prima apariie a acestei
s-expresii n lista l care satisface (f e ek) t, cu f o funcional ce
necesit exact doi parametri, atunci:
(member e l :test #f) (ek.((en.nil)))
Implicit, Common Lisp consider funcionala de test ca fiind eql. Cnd
se dorete utilizarea acesteia n test, ea poate fi ignorat:
(member ek l) (ek.((en.nil)))
> (member alpha (a alpha b alpha))
(ALPHA B ALPHA)
> (member 3 '(1 2 3 4 5) :test #'<)
(4 5)
> (member 3 '(1 2 3 4 5) :test #'evenp)
Error: EVENP got 2 args, wanted 1 arg.
> (member '(3 4) '(1 2 (3 4) 5))
NIL
> (member '(3 4) '(1 2 (3 4) 5) :test #'equal)
((3 4) 5)
> (setq x '(1 2 (3 4) 5))
(1 2 (3 4) 5)
> (member (caddr x) x)
((3 4) 5)

Funciile union i intersection realizeaz reuniunea, respectiv


intersecia, a dou liste, ca operaii cu mulimi, n sensul excluderii
elementelor identice. Testarea identitii, ca i n cazul funciei member, este
lsat la latitudinea programatorului. Testarea trebuie realizat cu o funcie
binar anunat de cuvntul cheie :test. Dac lipsete, testul de identitate se
face implicit cu eql. Ordinea elementelor n lista final este neprecizat.
Numai forma apelului este dat mai jos:
(union l1 l2 :test #f)
(intersect l1 l2 :test #f)
> (union '(a b c d) '(1 a 2 b 3))
(D C 1 A 2 B 3)
> (intersection '(a b c d) '(1 a 2 b 3))
(B A)

Testul excluderii poate las incert proveniena elementelor ce se


copiaz n lista final.
> (union '((a 1) (b 2) (c 3) (d 4)) '((a alpha) (c gamma) (e
epsilon)) :test #'(lambda(x y) (eql (car x) (car y))))
((D 4) (B 2) (A ALPHA) (C GAMMA) (E EPSILON))
> (intersection '((a 1) (b 2) (c 3) (d 4)) '((a alpha) (c
gamma) (e epsilon)) :test #'(lambda(x y) (eql (car x) (car
y))))
((C 3) (A 1))

n aceste exemple testul excluderii verific identitatea elementelor de pe poziia


car a listelor-perechi-de-elemente ce intr n componena argumentelor
originale. Perechile de elemente (a 1) i (a alpha), respectiv (c 3) i (c
gamma) rspund pozitiv la test. Proveniena elementelor selectate n listamulime final, n cazul lor, e nedecidabil.

Operaii logice i evaluri controlate


Operatorii logici n Lisp snt and, or i not. Dintre acetia and i or,
pentru c i evalueaz argumentele n mod condiionat, snt considerate i
structuri de control. Funcia not are acelai comportament ca i null. Dac
e=nil, atunci:
(not e) t
altfel:
(not e) nil
Macro-ul and primete un numr oarecare n1 de argumente pe care
le evalueaz de la stnga la dreapta pn cnd unul din ele ntoarce nil, caz
n care evaluarea se oprete i valoarea ntoars este nil. Dac, nici unul
dintre primele n-1 de argumente nu se evalueaz la nil, atunci and ntoarce
valoarea ultimului argument. Dac e1 nil, atunci:
(and e1 e2 en) nil
altfel, dac e2 nil, atunci:
(and e1 e2 en) nil
.a.m.d., altfel:
(and e1 e2 en) en
> (setq n 7)
7
> (and (not (zerop n)) (/ 1 n))
1/7
> (setq n 0)
0
> (and (not (zerop n)) (/ 1 n))
NIL

Macro-ul or primete un numr oarecare n1 de argumente pe care le


evalueaz de la stnga la dreapta pn cnd unul din ele ntoarce o valoare
diferit de nil, caz n care evaluarea se oprete i valoarea acelui argument
este i cea ntoars de or. Dac, toate primele n-1 de argumente se
evalueaz la nil, atunci or ntoarce valoarea ultimului argument. Dac e1
~nil, atunci:
(or e1 e2 en) e1
altfel, dac e2 ~nil, atunci:
(or e1 e2 en) e2
.a.m.d., altfel:
(or e1 e2 en) en

Forme pentru controlul evalurii


Cele mai utilizate forme Lisp pentru controlul explicit al evalurii snt if
i cond. Forma if poate fi chemat cu doi sau trei parametri. n varianta cu
trei parametri, evaluarea ei se face astfel: dac e1 ~nil, atunci:
(if e1 e2 e3) e2
altfel:
(if e1 e2 e3) e3
n varianta cu doi parametri, dac: e1 ~nil, atunci:
(if e1 e2) e2
altfel:
(if e1 e2) nil
> (if (zerop (setq x 0)) "eroare" (/ 1 x))
"eroare"
> (if (zerop (setq x 2)) "eroare" (/ 1 x))
1/2

cond este cea mai utilizat form de control a evalurii. Sintactic ea


este format dintr-un numr de clauze. Elementele de pe prima poziia a
clauzelor se evalueaz n secven pn la primul care e diferit de nil. n
acest moment celelalte elemente ale clauzei de evalueaz i cond ntoarce
valoarea ultimului element din clauz. Dac toate elementele de pe prima
poziie din clauze se evalueaz la nil, atunci cond nsui ntoarce nil. Dac
e11 ~nil, atunci:
(cond (e11 e1n1) (ek1 eknk)) e1n1
.a.m.d., altfel dac ek1 ~nil, atunci:
(cond (e11 e1n1) (ek1 eknk)) eknk
altfel:
(cond (e11 e1n1) (ek1 eknk)) nil
> (cond ((setq x t) "unu") ((setq x t) "doi") (t "trei"))
"unu"
> (cond ((setq x nil) "unu") ((setq x t) "doi") (t "trei"))
"doi"
> (cond ((setq x nil) "unu") ((setq x nil) "doi") (t "trei"))
"trei"

when i unless
Liste i tabele de asociaie
Listele de asociaie snt structuri frecvent folosite n Lisp pentru accesul
rapid la o dat prin intermediul unei chei. Elementele unei liste de asociaie
snt celule cons n care prile aflate n car se numesc chei i cele aflate n
cdr date. Pentru c introducerea i extragerea noilor elemente, de regul,
se face printr-un capt al listei, ele pot fi fcute s aib un comportament
analog stivelor. ntr-o astfel de structur introducerea unei noi perechi cheiedat cu o cheie identic uneia deja existent are semnificaia umbririi
asociaiei vechi, dup cum eliminarea ei poate s nsemne revenirea n

istorie la asociaia anterioar. Pe acest comportament se bazeaz, de


exemplu, legarea variabilelor la valori.
Funcia acons construiete o nou list de asociaie copiind o list
veche i adugnd o nou pereche de asociaie pe prima poziie a acesteia.
Ea nu modific lista de asociaie. Dac l ((e11.e21).(
((e1n.e2n).nil))), atunci:
(acons e1 e2 l) ((e1.e2).((e11.e21).(((e1n.e2n).nil))))
> (setq la (acons 'a 1 nil))
((A . 1))
> (acons 'b 2 la)
((B . 2) (A . 1))
> (acons 'a 3 la)
((A . 3) (A . 1))

Funcia pairlis organizeaz o list de asociaie din chei aflate ntr-o


list i date aflate n alta. Nu exist o ordine a-prioric de introducere a
elementelor n lista de asociaie. Evident, cele dou liste-argument trebuie s
aib aceeai lungime. Dac l1 (e11.((e1n.nil)) i l2 (e21.(
(e2n.nil)), atunci:
(pairlis l1 l2) ((e11.e21).(((e1n.e2n).nil)))
> (pairlis (list a b c) (list 1 2 3))
((C . 3) (B . 2) (A . 1))

Dac apare i un al treilea argument, care trebuie s fie o list de asociaie,


introducerea noilor elemente se face n faa celor deja existente n aceast
list.
Funciile asooc i rassoc snt funcii de acces ntr-o list de asociaie
assoc folosind cheia pentru identificarea elementului cutat, iar rassoc
data.
Dac: l ((e11.e21).(((e1k.e2k).(((e1n.e2n).nil))))), i
(e1k.e2k) este prima pereche cheie-dat din lista de asociaie n care apare
e1k pe poziia cheii, atunci:
(assoc e1k l) (e1k.e2k)
Pentru aceeai list de asociaie, dac (e1k.e2k) este prima pereche
cheie-dat din lista de asociaie n care apare e2k pe poziia datei, atunci:
(rassoc e2k l) (e1k.e2k)
> (setq vals (pairlis (list 'a 'b 'c) (list 1 2 3)))
((C . 3) (B . 2) (A . 1))
> (assoc 'b vals)
(B . 2)
> (rassoc 2 vals)
(B . 2)

n mod implicit, testul de identificare a cheii sau a datei, se face cu


funcia eql. Dac ns se dorete un alt tip de identificare, atunci o
funcional (care trebuie s aib exact doi parametri) poate fi anunat, prin
cuvntul cheie :test:

Dac l ((e11.e21).(((e1k.e2k).(((e1n.e2n).nil))))) i e1k


este prima apariie pe poziia cheii care satisface (f e e1k) t, cu f o
funcional care necesit exact doi parametri, atunci:
(assoc e l :test #f) (e1k.e2k)
n aceleai condiii pentru argumentul l, dar cu funcionala satisfcnd (f
e e2k) t:
(rassoc e l :test #f) (e1k.e2k)
> (rassoc 2 vals :test #'>)
(A . 1)
Exemplul probeaz i ordinea argumentelor funcionalei: ca prim argument al
fucionalei este considerat primul argument al funciei, iar ca cel de al doilea argument
pe rnd cte o dat din lista de asociaie.

Locaii i accese la locaii


Forma setf acceseaz o locaie, pentru completarea ei, prin
intermediul unei funcionale care, n mod uzual, solicit o valoare depozitat
n acea locaie. Setf, aadar, inverseaz comportamentul funcionalei, care,
n loc de a extrage o valoare deja depozitat ntr-o locaie va deschide calea
pentru a se memora acolo o valoare. Dac f1 ea, , fn ez atunci:
(setf f1 e1 fn en) en|f1 e1, , fn en
> (setq l '(a b c))
(A B C)
> (setf (car l) 1 (cadr l) 'beta)
BETA
> l
(1 BETA C)

Oricare dintre urmtoarele funcionale poate fi utilizat ca prim argument:


toate funciile c....r, nth. Alte funcionale vor fi adugate la aceast list
pe msur ce vor fi introduse.
Lista de proprieti a unui simbol
Unui simbol i se poate asocia o list de perechi proprietate-valoare, n
care proprietile snt simboluri, iar valorile date Lisp. n aceast list o
proprietate poate s apar o singur dat. O list de proprieti are asemnri
cu o list de asociaie (astfel numele de proprietate corespunde cheii, iar
valoarea proprietii datei) dar exist i diferene ntre ele (n lista de
proprieti o singur valoare poate fi atribuit unei proprieti dar mai multe n
lista de asociaie, ce pot fi regsite n ordinea invers a atribuirilor).
Implementaional, o list de proprieti a unui simbol este o list de lungime
par, n care pe poziiile impare snt memorate (cu unicitate) numele
proprietilor i alturi de ele, pe poziiile pare, valorile acestora.
Cercetarea valorii unei proprieti a unui simbol se face prin funcia
get: dac lista de proprieti a simbolului s este: (s1.(e1.((sn.
(en.nil))))), atunci, dac sk {s1,,sn}:

(get s sk) ek
altfel:
(get s sk) nil
n cazul n care un al treilea argument e specificat, atunci el indic
valoarea care se dorete s nlocuiasc valoarea implicit ntoars nil atunci
cnd proprietatea solicitat nu a fost setat: dac sk {s1,,sn}:
(get s sk e) e
Atribuirea valorii unei proprieti a unui simbol se face printr-un apel
setf n care pe poziia funcionalei se folosete get:
> (setf (get 'a 'p1) 'v1)
V1
> (setf (get 'a 'p2) 'v2)
V2
> (get 'a 'p1)
V1
> (get 'a 'p2)
V2

Funcia remprop ndeprteaz o valoare pe de o proprietate a unui


simbol: dac lista de proprieti a simbolului s este:
l=(s1.(e1.((sk.(ek.((sn.(en.nil)))))))), astfel nct:
(get s sk) ek
atunci:
(remprop s sk) ~nil | l(s1.(e1.((sn.(en.nil)))))
Funcia symbol-plist ntoarce lista de proprieti a unui simbol:
Dac lista de proprieti a simbolului s este: (s1.(e1.((sn.(en.nil))
))), atunci:
(symbol-plist s) (s1.(e1.((sn.(en.nil)))))
> (setf (get 'a 'p1) 'v1)
V1
> (setf (get 'a 'p2) 'v2)
V2
> (symbol-plist 'a)
(P2 V2 P1 V1)

Funcii chirurgicale
Funciile chirurgicale i justific numele prin faptul c realizeaz
modificri asupra argumentele. Ele snt aadar funcii n care efectul lateral
este cel care primeaz.
Funcia nconc modific toate argumentele (fiecare de tip list) cu
excepia ultimului, realiznd o list cu toate elementele listelor componente.
Astfel, dac:
l1 = (e11.((e1k1.nil))), ln-1 = (en-1,1.((en-1,kn-1.nil))),
ln = (en1.((enkn.nil)))
atunci:

(nconc 'l1 'ln-1 'ln)


(e11.((e1k1.((en-1,1.((en-1,kn-1. (en1.((enkn.nil))))))))))
| l1 (e11.((e1k1.((en1.((enkn.nil))))
,
ln-1 (en-1,1.((en-1,kn-1.(en1.((enkn.nil))))))
Notaia pune n eviden faptul c primele n-1 argumente, din cele n
ale apelului, vor rmne modificate n urma apelului.
> (setq x '(a b c))
(A B C)
> (setq y '(1 2 3))
(1 2 3)
> (setq z '(u v))
(U V)
> (nconc x y z)
(A B C 1 2 3 U V)
> X
(A B C 1 2 3 U V)
> y
(1 2 3 U V)
> z
(U V)

Funcia rplaca modific car-ul celulei cons obinut din evaluarea


primului argument la valoarea celui de al doilea argument i ntoarce celula
cons modificat. Dac: l = (e1.e2), atunci:
(rplaca l e) (e.e2) | l = (e.e2)
> (setq x (a b c))
(A B C)
> (rplaca (cdr x) d)
(D C)
> x
(A D C)

Funcia rplacd modific cdr-ul celulei cons obinut din evaluarea


primului argument la valoarea celui de al doilea argument i ntoarce celula
cons modificat. Dac: l = (e1.e2), atunci:
(rplacd l e) (e1.e) | l = (e1.e)
> (setq x (a b c))
(A B C)
> (rplacd (cdr x) d)
(B . D)
> x
(A B . D)
Urmtorul exemplu probeaz c un apel append copiaz toate elementele listelor
argument cu excepia ultimului:
> (setq x '(a b c) y '(d e))
(D E)
> (setq z (append x y))
(A B C D)
> (rplaca x 'alpha)
(ALPHA B C)

> z
(A B C D)
> (rplaca y 'beta)
(BETA E)
> z
(A B C BETA E)

Forme de apelare a altor funcii


Funcia apply cheam o funcie asupra unei liste de argumente.
> (setq f '+)
+
> (apply f '(1 2 3))
6
> (apply #'+ '())
0
> (apply #'min '(2 -6 8))
-6

Funcia funcall aplic o funcie asupra unor argumente.


> (cons 1 2)
(1 . 2)
> (setq cons (symbol-function '+))
#<Function +>
> (funcall cons 1 2)
3
> (funcall #'max 1 2 3 4)
4
> (setq x 2)
2
> (funcall (if (> x 0) #'max #'min) 1 2 3 4)
4
> (setq x -2)
-2
> (funcall (if (> x 0) #'max #'min) 1 2 3 4)
1

Lambda expresii
O lambda-expresie ataeaz unui set de parametri formali un corp de
funcie. O lambda-expresie poate fi folosit n locul unui nume de funcie:
((lambda (s1 sk) ec1 ecn) ep1 epk) ecn | s1ep1,,
skepk, ec1,, [ecn], unbind(s1,, sk)
Proceduri uzuale de apel snt: ca prim argument al unei liste supus
evalurii, sau prin intermediul funciilor apply, funcall ori map... (a se
vedea seciunea urmtoare). La evaluare, mai nti argumentele formale ale
lambda-definiiei (s1,, sk) se leag la valorile actuale evaluate (ep1,,
epk), apoi formele corpului definiiei (ec1 ecn) snt evaluate una dup alta.
Valoarea ntoars este cea rezultat din evaluarea ultimei forme. Lambdadefiniia marcheaz un context (domeniu) lexical pentru variabilele locale.
> ((lambda(x y) (> x y)) 3 2)

Lambda-funcii recursive 2
Nu putem utiliza o lambda definiie pentru o funcie recursiv pentru c
lambda-funcia nu are nume. Pentru a asocia nume funciilor definite ca
lambda-expresii se folosete construcia labels. Forma unui apel este:
(labels (<specificaie-legare>*) <apel>*)
n care fiecare dintre specificaiile de legare trebuie s aib forma:
(<nume> <parametri> . <corp>)
adic analog unei definiii lambda. n interiorul expresiilor din labels,
<nume> va referi acum o funcie ca dup un apel:
#'(lambda <parametri> . <corp>)
> (labels ((inc (x) (1+ x)))(inc 3))
4
n exemplul urmtor primul argument al lui mapcar este o funcie recursiv:
> (defun count-instances (obj lsts)
(labels ((instances-in (lst)
(if (consp lst)
(+ (if (eq (car lst) obj) 1 0)
(instances-in (cdr lst)))
0)))
(mapcar #'instances-in lsts)))
COUNT-INSTANCES
> (count-instances 'a '((a b c) (d a r p a) (d a r) (a a)))
(1 2 1 2)
Funcia primete un obiect i o list i ntoarce o list a numrului de apariii a
obiectului n fiecare element al listei.
> (labels((dot-product (a b)
(if (or (null a)(null b))
0
(+ (* (car a)(car b)) (dot-product (cdr a)(cdr
b)))
)))
(dot-product '(1 2 3) '(10 20 30)))
140
Apelul de mai sus cuprinde o definiie recursiv a produsului scalar al doi vectori (dai
ca liste).

Funcii de coresponden
Din aceast categorie fac parte funcii care aplic o funcional asupra
argumentelor construite din listele primite ca parametri.
2

Exemplele din aceast seciune snt preluate din [Graham, 1994].

Mapcar aplic o funcie asupra elementelor succesive ale unor liste:


dac l1 (e11.((e1k1.nil))), ln (en1.((enkn.nil))) i f
este o funcional de n argumente (aadar aritatea funcionalei este egal cu
numrul listelor comunicate ca parametri) i dac lungimea celei mai mici liste
argument este k (k = min (k1, , kn)), atunci:
(mapcar f l1 ln) = (list (funcall #f e11 en1)

(funcall #f e1k enk))


cu alte cuvinte, valoarea ntoars de mapcar va fi o list de lungime k, n care
fiecare element de rang j (j{1,, k}) reprezint valoarea ntoars de
evaluarea funcionalei f asupra elementelor de rang j din fiecare din listele
din intrare (v. i figura 5).
numrul argumentelor =
aritatea funcionalei

(mapcar

l1 ln)

(f

e11 en1 )

(f

e12 en2 ) e2

(f

e1
dimensiunea ieirii =
lungimea listei minime

e1k enk ) ek

Figura 5: Evaluarea n cazul unui apel mapcar


> (mapcar 'equal '(a (b c) d e) '(b (b c) f e 3))
(NIL T NIL T)
Urmtorul apel realizeaz produsul scalar al doi vectori3 dai ca liste de numere:
> (apply #'+ (mapcar #'* '(1 2 3) '(10 20 30)))
140
Urmtoarea secven intenioneaz s nlocuiasc nume de persoane cu diminutivele
lor ntr-un text dat:
> (setq l (pairlis '(Mihai Gheorghe Nicolae Ion) '(Misu Ghita
Nicu Ionica)))
((ION . IONICA) (NICOLAE . NICU) (GHEORGHE . GHITA) (MIHAI .
MISU))
> (mapcar #'(lambda(x) (if (null (assoc x l))
x
3

Produsul scalar al doi vectori de numere este numrul egal cu suma produselor elementelor de acelai
rang din cei doi vectori: dac v1=(a1,,an), v2=(b1,,bn), atunci v1.v2=a1*b1 ++ an*bn.

(cdr (assoc x l))))


'(Mihai s-a intilnit cu Mircea ca sa-l viziteze
impreuna pe Ion))
(MISU S-A INTILNIT CU MIRCEA CA SA-L VIZITEZE IMPREUNA PE
IONICA)

Aceast implementare sufer de un defect: execuia este ineficient


pentru c asocierea unui nume ntr-o list este fcut de dou ori. Vom
corecta aceast deficien n seciunea dedicat formei let.
Maplist funcioneaz asemntor cu mapcar, numai c funcionala
este aplicat listelor i cdr-urilor succesive ale acestora, n secven:
numrul argumentelor =
aritatea funcionalei

(maplist

#f

l1

ln)

(f

l1

ln

) e1

(f

(cdr l1)

(cdr ln)

) e
2

(f

(cdr((cdr l1))

(cdr((cdr ln))

dimensiunea
ieirii
=
lungimea listei
minime

)
ek

Figura 6: Evaluarea n cazul unui apel maplist


> (maplist #'(lambda (x) x) '(1 2 3 4))
((1 2 3 4) (2 3 4) (3 4) (4))
> (mapcar #'(lambda (x) (cons 'alpha x))
(maplist #'(lambda (x) x) '(1 2 3 4)))
((ALPHA 1 2 3 4) (ALPHA 2 3 4) (ALPHA 3 4) (ALPHA 4))
> (mapcar #'(lambda (x) (apply #'+ x))
(maplist #'(lambda (x) x) '(1 2 3 4)))
(10 9 7 4)

Definiii de funcii
Definiia unei funcii se face cu construcia defun:
(defun s (s1 sk) e1 en) s | s(lambda(s1 sk) e1 en)
Aadar, rezultatul unei definiii de funcii este crearea unei legri ntre un
nume, recunoscut global, s, o list de variabile, considerate variabile
formale ale funciei, s1 sk i un corp al funciei secvena e1 ek.
Dei neuzual, este permis, desigur, definirea de funcii n interiorul
altor funcii. O funcie definit n interiorul altei funcii devine cunoscut

sistemului ns numai dup apelarea cel puin o dat a funciei n care a fost
ea definit:
> (defun f1 () (princ "f1"))
F1
> (defun f2() (defun f3() (princ "f3")) (princ "f2"))
F2
> (f1)
f1
"f1"
> (f2)
f2
"f2"
> (f3)
f3
"f3"
> (defun f4() (defun f5() (princ "f5")) (princ "f4"))
F4
> (f5)
Error: attempt to call `F5' which is an undefined function.
[condition type: UNDEFINED-FUNCTION]
> (f4)
f4
"f4"
> (f5)
f5
"f5"

Tipuri de argumente n definiia unei funcii


Obligatorii, opionale, rest, cuvinte cheie i auxiliare...
Recursivitate
n Lisp recursia este la ea acas. Iat definiia funciei factorial:
> (defun fact (n) (if (zerop n) 1 (* n (fact (1- n)))))

Regula general n definirea funciilor recursive:


- ncepe prin a scrie condiia de oprire
- scrie apoi apelul recursiv.
> (defun fact (n)
(if (zerop n) 0
(* n (fact (- n 1)))))
FACT

Orice argument, orict de mare, poate fi dat acestei funcii:


>(fact 1000)
402387260077093773543702433923003985719374864210714632543799910
429938512398629020592044208486969404800479988610197196058631666
872994808558901323829669944590997424504087073759918823627727188
732519779505950995276120874975462497043601418278094646496291056
393887437886487337119181045825783647849977012476632889835955735

432513185323958463075557409114262417474349347553428646576611667
797396668820291207379143853719588249808126867838374559731746136
085379534524221586593201928090878297308431392844403281231558611
036976801357304216168747609675871348312025478589320767169132448
426236131412508780208000261683151027341827977704784635868170164
365024153691398281264810213092761244896359928705114964975419909
342221566832572080821333186116811553615836546984046708975602900
950537616475847728421889679646244945160765353408198901385442487
984959953319101723355556602139450399736280750137837615307127761
926849034352625200015888535147331611702103968175921510907788019
393178114194545257223865541461062892187960223838971476088506276
862967146674697562911234082439208160153780889893964518263243671
616762179168909779911903754031274622289988005195444414282012187
361745992642956581746628302955570299024324153181617210465832036
786906117260158783520751516284225540265170483304226143974286933
061690897968482590125458327168226458066526769958652682272807075
781391858178889652208164348344825993266043367660176999612831860
788386150279465955131156552036093988180612138558600301435694527
224206344631797460594682573103790084024432438465657245014402821
885252470935190620929023136493273497565513958720559654228749774
011413346962715422845862377387538230483865688976461927383814900
140767310446640259899490222221765904339901886018566526485061799
702356193897017860040811889729918311021171229845901641921068884
387121855646124960798722908519296819372388642614839657382291123
125024186649353143970137428531926649875337218940694281434118520
158014123344828015051399694290153483077644569099073152433278288
269864602789864321139083506217095002597389863554277196742822248
757586765752344220207573630569498825087968928162753848863396909
959826280956121450994871701244516461260379029309120889086942028
510640182154399457156805941872748998094254742173582401063677404
595741785160829230135358081840096996372524230560855903700624271
243416909004153690105933983835777939410970027753472000000000000
000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000
>

Abia un argument exagerat de mare, de genul:


>(fact (fact 1000))

ne poate cauza neplceri (atunci cnd interpretorul utilizat nu este unul


comercial):
Error: An allocation request for 1080 bytes caused a need for
2883584 more bytes of heap. This request cannot be satisfied
because you have hit the Allegro CL Trial heap limit.
[condition type: STORAGE-CONDITION]

Recursivitate coad4
O funcie se spune c este coad-recursiv dac, dup apelul recursiv,
nu mai are nimic de fcut. Urmtoarea funcie e coad-recursiv:
> (defun our-find-if (fn lst)
(if (funcall fn (car lst)
4

O parte din exemplele acestei seciuni snt preluate din [Graham, 1994].

(car lst)
(our-find-if fn (cdr lst))))

pentru c funcia ntoarce direct valoarea apelului recursiv. Urmtoarele funcii


nu snt coad-recursiv:
> (defun our-length (lst)
(if (null lst)
0
(1+ (our-length (cdr lst)))))
> (defun suma (lst)
(if (null lst) 0
(+ (car lst) (suma (cdr lst)))))

pentru c rezultatul apelului recursiv este transferat, ntr-un caz lui 1+ i, n


cellalt caz, funciei de adunare +. Nici funcia factorial, definit mai sus,
nu e coad-recursiv.
Recursivitatea coad este cutat pentru c multe compilatoare
Common Lisp pot transforma funcii coad-recursive n bucle (iteraii). O
funcie care nu e coad-recursiv poate adesea fi transformat ntr-una care e
coad recursiv prin scufundarea n ea a unei alte funcii ce conine un
argument acumulator (ce pstreaz valoarea calculat pn la un moment
dat).
Scrierea funciilor cu recursivitate coad prin parametru acumulator
Urmtoarea funcie ntoarce lungimea unei liste:
> (defun tail-length (lst acc)
(if (null lst) acc
(tail-length (cdr lst) (1+ acc))))

Funcia de adunare a elementelor unei liste, de mai sus, scris n


varianta cu registru acumulator:
> (defun suma1 (lst ac)
(if (null lst) ac
(suma (cdr lst) (+ ac (car lst)))))
> (suma1 '(1 2 3 4 5) 0)
0[1]: (SUMA1 (1 2 3 4 5) 0)
1[1]: (SUMA1 (2 3 4 5) 1)
2[1]: (SUMA1 (3 4 5) 3)
3[1]: (SUMA1 (4 5) 6)
4[1]: (SUMA1 (5) 10)
5[1]: (SUMA1 NIL 15)
5[1]: returned 15
4[1]: returned 15
3[1]: returned 15
2[1]: returned 15
1[1]: returned 15
0[1]: returned 15
15

Apelul acestei funcii trebuie ncapsulat ntr-un alt apel care s


iniializeze acumulatorul:

> (defun suma (lst)


(suma1 lst 0))

Acelai lucru poate fi ns realizat cu ajutorul lui labels astfel nct


funcia recursiv s fie ascuns n interiorul alteia care nu afieaz
argumentul acumulator. n acest fel "buctria" apelului recursiv nu se
manifest la suprafa:
> (defun our-length (lst)
(labels ((rec (lst acc)
(if (null lst)
acc
(rec (cdr lst) (1+ acc)))))
(rec lst 0)))

Forme de secveniere
progn este o form al crei scop este pur i simplu acela de a evalua
ntr-o secven o succesiune de forme Lisp n vederea ntoarcerii ultimei
valori:
(progn e1 en) en | e1,, [en]
prog1 se comport la fel ca progn, cu deosebirea c valoarea
ntoars este cea a primei forme din cuprinderea sa:
(prog1 e1 en) e1 | [e1],, en
Utiliznd formele progn i prog1 se pot realiza mai multe evaluri n
contexte sintactice n care doar o singur form este permis (ca de exemplu,
toate cele trei poziii ale argumentelor formei if). Este clar c rostul utilizrii
unui prog1 ntr-o definiie de funcie este acela de a ntoarce o valoare
nainte efecturii unor evaluri care snt importante numai prin efectele lor
laterale.
Variabile i domeniile lor
n Lisp noiunea de variabil, att de comun n alte limbaje de
programare, neleas ca asocierea dintre un nume simbolic, un tip i un
spaiu de memorie unde poate fi depozitat o valoare, trebuie privit oarecum
diferit. Vom continua s numit variabil un simbol care apare n codul
programului cu intenia ca lui s i se asocieze valori. Mai nti, aa cum am
artat deja (v. seciunea Tipuri de date n Lisp), variabilele nu au tipuri. Apoi,
nu este cazul ca o variabil s defineasc un spaiu de memorie care s fie
ocupat de o valoare, ci simbolului care d numele variabilei i se poate asocia
o valoare.
Un numr de fome ale Lisp-ului (printre ele: defun, lambda, let, do,
dolist, dotimes etc., adic acele forme ce permit definirea de parametri
locali) creeaz domenii (sau ntinderi) ale variabilelor pe post de parametri
locali. Un domeniu este un spaiu lexical contiguu, mrginit de perechea de

paranteze care mbrac o form, care evideniaz o list de parametri locali.


La orice moment funcioneaz urmtoarea regul de legare: valoarea unei
variabile este dictat de ultima legare ce a avut loc n cel mai adnc domeniu
care cuprinde variabila n lista de parametri locali ai si. Dac nu exist nici
un domeniu care s numere variabila printre parametrii si atunci variabila e
considerat global i legarea se face n domeniul de adncime zero, cel al
expresiilor evaluate la prompter.
Astfel, n figura urmtoare snt schiate trei domenii, n afara celui
global (considerat de adncime zero): cel exterior (sau de adncime unu),
notat cu A, care definete ca locale variabilele x, u i w, i dou domenii de
adncime doi, notate B i respectiv C, care au ca variabile locale, B pe x i y
,iar C pe x, z i u. n contextul domeniului B valorile variabilelor referite snt:
pentru x i y cele legate n acest context, pentru u cea legat n contextul
A i pentru z cea definit n contextul global. n contextul domeniului A, n
continuare snt referite variabilele: x cu valoarea legat n contextul A i v
cu valoare global. n sfrit, n domeniul C snt referite variabilele: x, z i u
cu valorile date de legrile domeniului C, i w cu legarea din contextul
domeniului A. O variabil care nu este definit ca local ntr-un domeniu se
spune c este liber n acel domeniu. Astfel, de exemplu, domeniul A conine
variabila liber v, domeniul B variabilele u i y, iar domeniul C variabila w.
(x,u,w)

(x,y) x,y,u,z

x,v

(x,z,u) x,z,u,w

Figura 7: Domenii lexicale incluse

Teoretic, exist dou modaliti prin care putem s asociem o valoare


unei variabile ce nu este parametru local al unei funcii: prin legare static i
prin legare dinamic. n legarea static, valoarea unei astfel de variabile este
stabilit de contextul lexical (ntinderea de program) n care este ea folosit,
pe cnd n legarea dinamic atribuirea valorii rezult n urma execuiei. O
valoare a unei variabile legat lexical poate fi referit numai de forme ce apar
textual n interiorul construciei ce a produs legarea. O astfel de legare
impune aadar o limitare spaial, iar nu una temporal, a domeniului unde
referina se poate realiza n siguran. Dimpotriv, o valoare a unei variabile
legat dinamic poate fi referit n siguran n toate momentele ulterioare
legrii pe parcursul evalurii formei n care a fost efectuat legarea. Ca
urmare ea impune o limitare temporal asupra apariiei referinelor iar nu una
spaial.
Tipul de legare considerat implicit n Common Lisp este cea static,
dei nu ntotdeauna lucrurile au stat aa. ntr-adevr n multe implementri
anterioare ale Lisp-ului (ca de exemplu, realizrile romneti DM-LISP
[Giumale .a., 1987] i TC-LISP [Tufi, 1987] [Tufi, Popescu, 1987]) se
optase pentru o legare dinamic. Legarea static este o trstur a majoritii
limbajelor moderne i ea asigur o mai mare rezisten la erori programelor.
O strategie de legare n care valorile atribuite variabilelor s depind de firul
curent al execuiei a constituit, la un moment dat, o alternativ de
implementare atrgtoare, din cauza posibilitilor mai bogate, aproape

exotice, am putea spune, de comunicare a valorilor variabilelor n timpul


execuiei, pe care aceast opiune le oferea. Experiena a artat ns c un
astfel de comportament predispunea la erori i, ca urmare, standardul
Common Lisp, fr a-l invalida, nu l recomand. El poate fi ales explicit de
utilizator printr-o declaraie special (v. cap. 9 Declarations n [Steele, 1990]).
> (defun F1()
(let ((x 'a))
(defun F2()
(prin1 x)
)
)
)
F1
> (F1)
F2
> (let ((x 'b))(F2))
A
A
Exemplul dorete s evidenieze cele dou posibiliti de legare ale variabilei x.
Definiia funciei F1 cuprinde o legare a simbolului x la valoarea a urmat de o
definiie a funciei F2, n care valoarea lui x este tiprit. Corpul definiiei funciei F2
creeaz un domeniu pentru x. n F2 variabila x nu este parametru formal i deci aici
nu avem un nou domeniu pentru x. Definiia funciei F2 are loc odat efectuat apelul
lui F1. Mai departe are loc apelul lui F2 ntr-un context n care x este legat la
valoarea b (domeniu precizat de forma let, ce va fi prezentat n seciunea
urmtoare). Un comportament tipic legrii dinamice ar trebui s produc tiprirea
valorii B, pentru c simbolul x era legat la aceast valoare nainte de apelul funciei
F2 n care are loc tiprirea. Faptul c valoarea tiprit este ns A semnaleaz un
comportament tipic legrii statice, adic unul n care simbolul x din contextul formei
let este altul dect cel din corpul definiiei lui F2. Pentru alt exemplu v. i [Graham,
1994], p. 16.

Forma special let i domenii ale variabilelor


Forma let ofer un mijloc de a defini variabile a cror semnificaie s
fie aceeai ntr-o anumit ntindere de program. Rostul ei este, aadar, de a
defini domenii lexicale i de a preciza legri. Cel mai frecvent tip de apel al
formei let are forma:
(let ((s1 ei1) (sn ein)) ec1 eck) eck | ei1,, ein,
s1ei1 snein, ec1,, [eck], unbind(s1,, sn)
n acest apel, s1 sn snt variabile locale, ei1 ein snt expresii care,
evaluate, configureaz valorile iniiale pe care le iau variabilele nainte de
evaluarea n secven a expresiilor ec1 eck. Valoarea ntoars de let
este cea a ultimei expresii, eck.
Definiia de mai sus caut s surprind, n seciunea de efecte laterale,
secvena evalurilor, cu precdere faptul c evaluarea secvenei ec1 eck
se face n contextul iniial al legrilor variabilelor s1,, sn la valorile iniiale
ei1,, ein, legrile fiind fcute n paralel dup ce valorile iniiale au fost
evaluate serial. Aceste legri snt ns uitate la ieirea din form. ntr-adevr,

dincolo de grania acestei construcii, semnificaia variabilelor locale, ca i


legrile realizate, se pierd. Notaia noastr mai comunic i ideea c valoarea
ntoars este ntr-adevr acel eck, obinut la un anumit moment n secvena
evalurilor, dup care contextul legrilor este uitat.
Legarea simultan a variabilelor la valori face posibil schimbarea ntre
ele a valorilor a dou variabile fr intermediul unei a treia variabile care s
memoreze temporar valoarea uneia dintre ele:
> (let ((x a)(y b)) (prin1 x) (prin1 y)
(let ((x y)(y x)) (prin1 x) (prin1 y)))
ABBA
A

Folosind forma let putem eficientiza soluia exerciiului cu diminutive


dat mai sus:
> (mapcar #'(lambda(x) (let ((temp (assoc x l)))
(if (null temp) x (cdr temp))))
'(Mihai s-a intilnit cu Mircea ca sa-l viziteze
impreuna pe Ion))
(MISU S-A INTILNIT CU MIRCEA CA SA-L VIZITEZE IMPREUNA
IONICA)

PE

Forma let* este similar formei let, cu deosebirea c asignarea


valorilor variabilelor este fcut serial. Cel mai frecvent tip de apel al formei
let* are forma:
(let* ((s1 ei1) (sn ein)) ec1 eck) eck | ei1, s1ei1, ,
ein, snein, ec1,, [eck], unbind(s1),, unbind(sn)
Desigur, cu let* rezultatul permutrii de valori de mai sus nu se mai
pstreaz:
> (let ((x a)(y b)) (prin1 x) (prin1 y)
(let* ((x y)(y x)) (prin1 x) (prin1 y)))
ABBB
B

Exemplele care urmeaz pun n eviden diverse domenii create cu


let i let*:
> (let ((x 1)) (prin1 x)
(let ((x 2)(y x)) (prin1 x)(prin1 y))
(prin1 x))
1211
1
> (let ((x 1)) (prin1 x)
(let* ((x 2)(y x)) (prin1 x)(prin1 y))
(prin1 x))
1221
1

Forme de iterare

dolist itereaz aceleai evaluri asupra tuturor elementelor unei


liste. Dac l = (e1.((ek.nil))), atunci:
(dolist (s l e) ec1 ecn) e | se1, ec1,, ecn,,
sek, ec1,, ecn, [e], unbind(s)
> (dolist (x '(a b c d))
(prin1 x)
(princ " ")
)
A B C D
NIL

ntr-o alt form a apelului, valoarea ntoars poate fi ignorat:


(dolist (s l) ec1 ecn) nil | se1, ec1,, ecn,, sek,
ec1,, ecn, [nil], unbind(s)
dotimes itereaz aceleai evaluri de un numr anumit de ori: Dac
n>0, atunci:
(dolist (s n e) ec1 ecn) e | s0, ec1,, ecn,,
sn-1, ec1,, ecn, [e], unbind(s)
> (dolist (x '(a b c d))
(prin1 x)
(princ " ")
)
A B C D
NIL

ntr-o alt form a apelului, valoarea ntoars poate fi ignorat:


(dolist (s l) ec1 ecn) nil | s0, ec1,, ecn,,
sn-1, ec1,, ecn, [nil], unbind(s)
Forma do reprezint maniera cea mai general de a organiza o iteraie
n Lisp. Ea permite utilizarea unui numr oarecare de variabile i de a le
controla valorile de la un pas al iteraiei la urmtorul. Forma cea mai
complex a unui apel do este:
(do ((s1 ei1 es1) (sn ein esn)) (et er1 erp) ec1 ecq)
erp | ei1,, ein, s1ei1 snein, while(not et){ec1,, ecq,
es1,, esn, s1es1 snesn}, er1,, [erp], unbind(s1,, sn)
Primul element al formei este o list definind variabilele de control ale buclei,
valorile lor de iniializare i de incrementare. Astfel, fiecrei variabile i
corespunde o list format din numele variabilei, eventual valoarea iniial i,
cnd aceasta apare, eventual o form de incrementare a pasului. Dac
expresia de iniializare e omis, ea va fi implicit luat nil. Dac expresia de
incrementare este omis, variabila nu va fi schimbat ntre pai consecutivi ai
iteraiei (dei corpul lui do poate modifica valorile variabilei prin setq).
nainte de prima iteraie, toate formele de iniializare snt evaluate i
fiecare variabil este legat la valoarea de iniializare corespunztoare

(acestea snt legri iar nu setri, astfel nct dup ieirea din iteraie,
variabilele revin la valorile la care erau legate nainte de intrarea n iteraie).
La nceputul fiecrei iteraii, dup procesarea variabilelor, o expresie de
test et este evaluat. Dac rezultatul este nil, execuia continu cu
evaluarea formelor din corpul do-ului: ec1,, ecq. Dac et este diferit de nil
se evalueaz n ordine formele er1,, erp, ultima valoare fiind i cea ntoars
de do.
La nceputul oricrei iteraii, cu excepia primeia, variabilele snt
actualizate astfel: toate formele de incrementare snt evaluate de la stnga la
dreapta i rezultatele snt asignate variabilelor n paralel.
n cazul formei do*, nainte de prima iteraie, evaluarea formelor de
iniializare urmat de asignarea lor variabilelor, este fcut serial pentru toate
variabilele:
(do* ((s1 ei1 es1) (sn ein esn)) (et er1 erp) ec1
ecq) erp | ei1, s1ei1,, ein, snein, while(not et){ec1,,
ecq, es1, s1es1,, esn, snesn}, er1,, [erp], unbind(s1,, sn)
Exemplele urmtoare exploateaz legrile paralele ale variabilelor de
index. n prima definiie, la fiecare pas oldx se leag la valoarea precedent
a lui x (exemple preluate din [Steele, 1990]):
(do ((x e (cdr x))
(oldx x x))
((null x))
body)
(defun list-reverse(lst)
(do ((x lst (cdr x))
(y '() (cons (car x) y)))
((endp x) y)))

Variabile i valorile lor


Chestiunile discutate pn acum probeaz c exist dou maniere n
care o variabil poate cpta o valoare: prin legare (exemplu: let) i prin
asignare (exemplu: setq). Orice construcie care leag o variabil la o nou
valoare salveaz vechea valoare, dac noul domeniu este inclus ntr-unul
vechi, astfel nct la ieirea din construcia care a produs noua legare variabila
revine la vechea valoare.
n realitate, nu exist nici o diferen asupra rezultatelor evalurilor
ntre cele dou maniere prin care o variabil poate cpta o valoare.
Exemplul urmtor lmurete:
> (let
ABA
A
> (let
x))
ABB
B
> (let
(prin1
ACA

((x 'a)) (prin1 x) (let ((x 'b)) (prin1 x)) (prin1 x))
((x 'a)) (prin1 x) (let () (setq x 'b) (prin1 x)) (prin1

((x 'a)) (prin1 x) (let ((x 'b)) (setq x 'c) (prin1 x))
x))

A
n primul exemplu, avem dou domenii incluse unul n altul, pentru variabila x. n
domeniul exterior x se leag la valoarea A, la intrarea n domeniul interior x se leag
la valoarea B, iar la ieirea din acesta revine la vechea valoare B.
n exemplul al doilea, x este liber n domeniul interior, dar i este asignat acolo
valoarea B. La ieirea din acest domeniu, care nu este al su, e normal ca x s
pstreze aceast valoare.
n al treilea exemplu, x este legat din nou n ambele domenii i, ulterior legrii
interioare la valoarea B, i este asignat o a treia valoare C. La revenirea n
domeniul exterior, x recapt valoarea la care era legat acolo A.

Valoarea pe care o poate avea o variabil cnd nu s-a realizat nici o


legare explicit a ei se consider global. O valoare global poate fi dat
numai prin asignare.
Definiie. Un simbol s apare liber ntr-o expresie cnd e folosit ca variabil n
acea expresie, dar expresia nu creeaz o legare pentru el. Astfel, w, x i z
apar libere n list iar w i y apar libere n let:
(let ((x y) (z 10)) (list w x z))

Despre o variabil care este liber ntr-un domeniu, precum i n toate


domeniile care-l nglobeaz pe acesta, i care nu are asignat o valoare n
domeniul global vom spune c e nelegat (unbound).
n seciunea Asignarea unei valori unui simbol discutam modaliti diferite de
interpretare a unei notaii de genul x:=y. Aparent cea mai simpl, prima
interpretare discutat este n realitate cea mai subtil: ea dorea ca variabila x
s primeasc valoarea pe care o are variabila y, dup care ele s aib viei
separate, n sensul c orice modificri efectuate asupra uneia s nu se
resfrng i asupra celeilalte. S observm c nici asignarea simpl, de genul
(setq x y), i nici legarea, de genul (let* ((y ...)(x y))...) nu
realizeaz acest deziderat pentru c exist pericolul ca modificrile asupra
uneia dintre variabile s fie rezultatul interveniei unei funcii chirurgicale, caz
n care ea ar fi resimit de ambele variabile. Am avea aadar cel de al doilea
comportament comentat, care este unul n care cele dou variabile devin
surori siameze. Pentru a realiza primul efect este necesar copierea valorii
simbolului y ca valoare a lui x. Common-Lisp dispune de mai multe funcii de
copiere. Astfel dac y este o list, putem scrie: (setq x (copy-list y)).
n ultima interpretare variabilei x i se atribuie ca valoare nsui simbolul y:
(setq x y).
Valori multiple i exploatarea lor
Dup cum s-a putut vedea, o seam de funcii, printre care floor ct
i ceiling, ntorc valori multiple. O generare explicit de valori multiple se
poate face cu forma values-list:
> (values-list (list 'a 'b 3 'c))
A

B
3
C

Cea mai simpl metod de exploatare a valorilor multiple const n


transformarea lor n liste. Forma multiple-value-list face acest lucru:
> (multiple-value-list (ceiling 7 3))
(3 -2)

Se observ c values-list i multiple-value-list snt inverse una alteie:


> (multiple-value-list (values-list (list 'a 'b 3 'c)))
(A B 3 C)

Forma multiple-value-call transfer valori multiple, ca


argumente, unui apel de funcie. Funcionala care asambleaz valorile
multiple trebuie dat ca prim argument al apelului:
> (multiple-value-call #'list 1 (ceiling 7 3) (values 5 6)
'alpha)
(1 3 -2 5 6 ALPHA)

O form (macro) care produce legri (genereaz domenii) prin


exploatarea valorilor multiple este multipe-value-bind. Sintaxa
(simplificat) este urmtoarea:
(multiple-value-bind ({var}*) values-form {form}*)

{result}*

n care var reprezint un nume de variabil, values-form este o form


care genereaz valori multiple, iar form reprezint corpul n care legrile snt
valorificate.
Ultima
form
evaluat
genereaz
valoarea/valorile
ntoars/ntoarse de macro.
> (multiple-value-bind (x y) (ceiling 7 3) (list x y))
(3 -2)

nchideri5
Combinaia dintre o funcie i un set de legri de variabile libere ale
funciei la momentul apelului acelei funcii se numete nchidere (closure).
nchiderile snt funcii mpreun cu stri locale.
> (defun list+ (lst n)
(mapcar #'(lambda (x) (+ x n))
lst))
> (list+ '(1 2 3) 10)
(11 12 13)

Exemplele din aceast seciune snt preluate din [Graham, 1994].

Urmtoarele funcii mpart o variabil comun ce servete de


numrtor. nchiderea contorului ntr-un let n loc de a-l considera o variabil
global l protejeaz asupra referirilor accidentale.
> (let ((counter 0))
(defun new-id () (incf counter))
(defun reset-id () (setq counter 0)))

n urmtorul exemplu avem o funcie care la fiecare apel ntoarce o


funcie mpreun cu o stare local:
> (defun make-adder (n)
#'(lambda (x) (+ x n)))
> (setq add2 (make-adder 2)
add10 (make-adder 10))
#<Interpreted-Function BF162E>
> (funcall add2 5)
7
> (funcall add10 3)
13

Funcia make-adder primete un numr i ntoarce o nchidere, care, atunci


cnd e chemat, adun numrul la argument. n aceast variant n
nchiderea ntoars de make-adder starea intern e constant. Urmtoarea
variant realizeaz o nchidere a crei stare poate fi schimbat la anumite
apeluri:
> (defun make-adder-b (n)
#'(lambda (x &optional change)
(if change (setq n x) (+ x n))))
> (setq addx (make-adder-b 1))
#<Interpreted-Function BF1C66>
> (funcall addx 3)
4
> (funcall addx 100 t)
100
> (funcall addx 3)
103

Transferul argumentelor n funcii


n transferul prin valoare naintea evalurii funciei, valorile actuale
ale parametrilor se copiaz ca valori ale parametrilor formali ai funciei.
Modificri ale valorilor parametrilor formali n timpul evalurii funciei nu
afecteaz valorile parametrilor actuali:
F(a)

a:

v1

F(x)
x := v2

x:

v1

n momentul apelului

x:

v2

n timpul evalurii lui F

Figura 8: Transferul prin valoare n general

n transferul prin referin numele parametrilor formali reprezint


sinonime ale numelor parametrilor actuali. Modificarea valorilor parametrilor
formali n timpul evalurii funciei provoac astfel o schimbare a nsi
valorilor parametrilor actuali. Aceast modificare reprezint, aadar, un efect
lateral al evalurii funciei:
a:

F(a)

F(x)
x := v2

v1

x:a:

v1

n momentul apelului

x:a:

v2

n timpul evalurii lui F

Figura 9: Transferul prin referin n general

Transferul n Lisp nu este nici prin valoare nici prin referin, dar poate
simula ambele tipuri: n Lisp un parametru formal se leag la valoarea
comunicat prin parametru actual. Atunci cnd, n interiorul funciei, are loc o
asignare a unei noi valori variabilei formale, ea este dezlegat de la valoarea
veche i legat la una nou:
F(a)

(defun F(x)
(setq x v2)
)

v1

n momentul apelului

v2

n timpul evalurii lui F

Figura 10: Transferul prin valoare n Lisp

Comportamentul este, aadar, acela al unui transfer prin valoare att


timp ct n corpul funciei se realizeaz numai deasignri ale valorilor
parametrilor formali. ndat ns ce n corpul funciei au lor modificri
chirurgicale ale valorii parametrilor formali, funcionarea capt trsturile
unui transfer prin referin:

F(a)

(defun F(x)
(rplaca x v2)
)

v1

n momentul apelului

v2

n timpul evalurii lui F

Figura 11: Transferul prin referin n Lisp

pentru c n acest mod parametrul actual simte transformrile structurii


accesate temporal prin parametrul formal.
Macro-uri. Definiie, macroexpandare i evaluare
Un macro este n esen o funcie care definete o funcie. Macro-urile
snt i mijloace prin care sintaxa Lisp-ului poate fi extins. Evaluarea
apelurilor de macro-uri este un proces n doi pai: nti o expresie specificat
n definiie este construit, apoi aceast expresie este evaluat. Primul pas
cel al construirii macro-expresiei se numete macroexpandare.
O definiie de macro, analog unei definiii de funcie, conine trei
elemente: un simbol care d numele macro-ului, o list de parametri i corpul.
ntr-un apel de funcie definit de utilizator, parametrii actuali snt
evaluai nainte ca parametrii formali din definiie s se lege la acetia.
Rezult c o evaluare difereniat a parametrilor nu poate fi realizat printr-o
definiie de funcie. Toate formele Lisp-ului n care parametrii se evalueaz
difereniat snt realizate intern ca macro-uri. Dac ar fi s realizm o funcie
care s aib comportamentul unui if, de exemplu, utiliznd nsi forma if
pentru aceasta, o definiie precum urmtoarea:
> (defun my-if(test expr-da expr-nu)
(if test expr-da expr-nu))

nu satisface, pentru c la intrarea n funcie toi cei trei parametri snt evaluai.
Astfel, ntr-un apel n care am dori s atribuim variabilei x valoarea DA sau NU,
n funcie de un argument, de genul (my-if t (setq x da) (setq x nu)),
cu toate c testul se evalueaz la T, x ar fi nti setat la DA i apoi la NU, el
rmnnd n cele din urm cu aceast valoare. Apelul de funcie ntoarce ns
valoarea celui de al doilea parametru:
> (my-if t (setq x da) (setq x nu))
DA
> x
NU

Un apel de macro poate include un alt apel de macro. Evaluarea unui


apel al acestuia produce, mai nti, expandarea lui, genernd inclusiv un apel
al macro-ului interior. Urmeaz apoi faza de evaluare propriu-zis a macroului exterior n care, la un moment dat, se lanseaz evaluarea macro-ului
interior. Ca urmare, la rndul lui, acesta mai nti se expandeaz i, ntr-o a
doua faz abia, se evalueaz. Procesul poate continua n acelai mod pe
oricte niveluri de apeluri de macro-uri n macro-uri, ceea ce presupune
inclusiv posibilitatea de a scrie definiii de macro-uri recursive.
Macroexpandrile au loc la momente diferite n diverse implementri.
Comportamentul descris mai sus este tipic unei implementri de genul
interpretorului. Acesta ateapt momentul evalurii apelului de macro pentru a
face macroexpandarea macro-ului. Aceasta nseamn c orice macro trebuie
definit naintea codului care se refer la el i c dac un macro este redefinit,
orice funcie care refer macro-ul trebuie de asemenea redefinit.

Un compilator va efectua toate macroexpandrile la momentul


compilrii. Un apel de macro ce apare n corpul definiiei unei funcii va fi
expandat la momentul compilrii funciei, dar expresia (sau codul obiect
rezultat) nu va fi evaluat pn ce funcia nu e chemat. Rezult c un
compilator nu are cum trata apeluri recursive de macro-uri, pentru c el are
tendina de a expanda apelul interior nainte de evaluare, ceea ce duce la un
alt apel de macro, care trebuie i el expandat .a.m.d., fcnd astfel imposibil
controlarea acestui proces.
> (defmacro nthb (n lst)
`(if (= ,n 0) (car ,lst) (nthb (- ,n 1) (cdr ,lst))))
NTHB
> (nthb 2 '(a b c d e))
C
> (defmacro fact(n)
`(if (zerop ,n) 1 (* ,n (fact (- ,n 1)))))
FACT
> (fact 4)
24

Este important s facem distincia dintre aceste dou momente,


respectiv obiectele asupra crora opereaz ele, n evaluarea unui macro:
pasul macroexpandrii opereaz cu expresii, cel al evalurii cu valorile
lor.
Despre apostroful-stnga (backquote)
Un apostrof-stnga (`) construiete o form Lisp conform modelului
(template) care urmeaz dup el. Sintactic, el prefixeaz o list. La evaluare
orice form a listei va fi copiat, cu excepia:
a) formelor prefixate de virgul (,), care snt evaluate;
b) unei liste prefixat de o secven virgul-at (,@), care provoac
inserarea elementelor listei.
> (setq b 'beta d 'gamma)
GAMMA
> `(a ,b c ,d)
(A BETA C GAMMA)
> (setq x (a b c))
(A B C)
>`(x 1 ,x 2 ,@x)
(X 1 (A B C) 2 A B C)
> `(a (x 1) (,x 2))
(A (X 1) ((A B C) 2))

Apostroful-stnga nsui poate fi copiat ntr-o expresie prefixat cu


apostrof-stnga:
> (setq x '(a b c))
(A B C)
> `(a `b ,x)
(A `B (A B C))

Putem acum relua definiia unui macro cu comportamentul lui if:


> (defmacro my-if(test expr-da expr-nu)
`(if ,test ,expr-da ,expr-nu))
MY-IF
> (my-if t (setq x 'da) (setq x 'nu))
DA
> x
DA

Restricii privind folosirea virgulei i a virgulei-at:


virgula poate s apar numai n interiorul unei expresii prefixate cu
apostrof-stnga:
> `(a ,(cons ,x '(alpha beta)) ,x)
Error: Comma not inside a backquote. [file position = 12]

pentru ca elementele unei liste s fie expandate prin virgul-at, locul


acesteia trebuie s fie ntr-o secven. E o eroare a se scrie la
prompter: ,@x
obiectul de inserat trebuie s fie o list, cu excepia cazului n care
apare ca ultim element ntr-o list. Astfel expresia `(a ,@1) se va
evalua la (a . 1), dar `(a ,@1 b) va genera un mesaj de
eroare.
Virgule-at se folosesc cu predilecie n definiiile macro-urilor cu un numr
nedefinit de argumente.
Asupra manierei de construcie a macro-urilor6
Se ncepe prin a scrie un apel al macrou-lui ce se dorete a fi definit
urmat de expresia n care se dorete ca acesta s fie expandat. Din apelul de
macro se construiete lista de parametri alegnd nume de parametri pentru
fiecare argument. ntre rndul apelului i cel al expandrii se traseaz sgei
ntre argumentele corespunztoare:

Exemplele din aceast seciune i urmtoarele, pn la Cnd apare captura inclusiv, snt reproduse din
[Graham, 1994].

(memq x choices)

apelul

(member x choices :test #eq)

expandarea

(defmacro memq (obj lst)


`(member ,obj ,lst :test #eq))

definiia

Figura 12: Reguli de dezvoltare a macro-urilor

Cazul unui macro cu un numr oarecare de argumente. S


presupunem c vrem s scriem un while care primete un test i un corp
format dintr-un numr oarecare de expresii. Evaluarea lui va nsemna
buclarea expresiilor corpului att timp ct expresia din test ntoarce o valoare
diferit de nil. ncepem prin a scrie un eantion de apel de macro. Plecnd
de la acesta, construim lista de parametri ai macrou-lui, dar unde n afar de
parametrul care realizeaz testul vom descrie corpul lui while printr-un
parametru &rest sau &body.
Apel:
(my-while (not running-engine)
(look-at-engine)
(or (ask-advice)
(think))
(try-to-fix-the-motor)
(turn-on-the-key))
(defmacro while (test &rest body)

Scriem apoi expansiunea dorit sub apel i unim prin linii argumentele din
corpul de apel cu poziia lor din expansiune, dar unde secvena din
expansiune ce va corespunde unicului parametru din apel este grupat, ca
aici:
(do ()
((not(not running-engine)))
(look-at-engine)
(or (ask-advice)
(think))
(try-to-fix-the-motor)
(turn-on-the-key))

n corpul definiiei, apoi, parametrul &rest (sau &body) va fi prefixat cu


virgula-at:

(defmacro my-while (test &body body)


`(do ()
((not ,test))
,@body))

Cum se testeaz macro-expandrile?


Funcia macroexpand-1 arat primul nivel al expandrii. Funcia
macroexpand arat toate nivelurile unei expandri.
>(do ((w 3)
(x 1
(y 2
(z))
((> x
(princ
(princ

(1+ x))
(1+ y))
10) (princ z) y)
x)
y))

> (macroexpand-1 '(do ((w 3)


(x 1 (1+ x))
(y 2 (1+ y))
(z))
((> x 10) (princ z) y)
(princ x)
(princ y)))
(BLOCK NIL
(LET ((W 3) (X 1) (Y 2) (Z))
(TAGBODY
#:$CFNH
(WHEN # #)
(PRINC X)
(PRINC Y)
(PSETQ X # Y #)
(GO #:$CFNH)
#:$CFNI)
(PRINC Z)
Y))
T

Asupra destructurizrii
Forma destructuring-bind primete un ablon, un argument ce se
evalueaz la o list i un corp de expresii i evalueaz expresiile cu
parametrii din ablon legai la elementele corespunztoare din list:
> (destructuring-bind (x (y) . z) '(a (b) c d)
(list x y z))
(A B (C D))

Destructurizarea e posibil, de asemenea, n listele parametrilor


macro-urilor. ntr-o definiie de macro, defmacro, se permite ca listele de
parametri s fie structuri de liste arbitrare. Cnd un macro de acest fel e
expandat, componentele apelului vor fi asignate parametrilor ca printr-un
destructuring-bind:

> (defmacro our-dolist ((var list &optional result) &body body)


`(progn
(mapc #'(lambda (,var) ,@body)
,list)
(let ((,var nil))
,result)))
OUR-DOLIST
> (our-dolist (x '(a b c)) (print x))
A
B
C
NIL
> (our-dolist (x '(a b c) 'bravo) (print x))
A
B
C
BRAVO

Cnd s folosim macro-uri?


Snt buci de cod care pot fi scrise att ca macro-uri ct i ca funcii:
(defun 1+ (x) (+ 1 x))
(defmacro 1+ (x) `(+ 1 ,x))

Un while ns nu poate fi scris dect ca un macro:


(defmacro while (test &body body)
`(do ()
((not ,test))
,@body))

pentru c el integreaz expresiile pe care le primete ca body n corpul unui


do unde ele vor fi evaluate numai dac test ntoarce true. Printr-un macro
se pot controla evalurile argumentelor din apel. Orice operator care trebuie
s acceseze parametrii nainte ca acetia s fie evaluai trebuie scris ca
macro.
Argumente pro i contra utilizrii macro-urilor
Evaluarea la momentul compilrii:
(defun avg (&rest args)
(/ (apply #+ args) (length args)))
(defmacro avg (&rest args)
`(/ (+ ,@args) ,(length args)))

n prima definiie (length args) este evaluat la momentul rulrii pe cnd


n cea de a doua la momentul compilrii:
> (macroexpand-1 '(avg 1 3 4 6))
(/ (+ 1 3 4 6) 4)
T

Integrarea cu Lisp-ul. Captura variabilelor


Aa numita captur a variabilelor poate s apar n dou moduri:
prin argumentele macro-ului: un simbol transmis ca argument ntrun apel de macro refer o variabil stabilit de ctre expandarea
macro-ului:
> (defmacro my-for ((var start stop) &body body)
`(do ((,var ,start (1+ ,var))
(limit ,stop))
((> ,var limit))
,@body))
> (my-for (x 1 5) (princ x))12345
NIL
> (my-for (limit 1 5) (princ limit))
;; Error: Lambda-list error: Variable LIMIT has been repeated.

prin simboluri libere: un simbol n definiia de macro poate provoca


o coliziune cu o legare n mediul n care macro-ul este expandat.

Definiie. Scheletul unei expansiuni de macro este format din ntreaga


expresie cu excepia oricrei pri ce reprezint un argument n apelul de
macro:
(defmacro foo (x y)
`(/ (+ ,x 1) ,y))
(foo (- 5 2) 6)

Scheletul expansiunii acestui apel este:


(/ (+

1)

Definiie: Un simbol este capturabil ntr-o expandare de macro dac:


a. el apare liber n scheletul macro-expandrii,
(defmacro cap1 ()
`(+ x 1))

sau
b. e legat ntr-o parte a scheletului n care argumentele comunicate
macro-ului snt fie legate fie evaluate.
(defmacro cap2 (var)

O aplicaie: un program care se autoperfecioneaz

Mai jos, simbolului animal i este atribuit o expresie care, dac este
lansat n evaluare, iniiaz un dialog ce duce la recunoaterea unui animal.
Atunci cnd recunoaterea este eronat, programul cere interlocutorului
informaii pentru rafinarea dialogului. Secvena nou de dialog astfel generat
este inserat n program astfel nct la o evaluare ulterioar arborele de
decizie al programului este mai complet. n acest fel, prin rulri repetate,
cunoaterea programului asupra diferitelor animale se perfecioneaz.
(setq animal
'(let ((int)(aniF))
(print "Animalul are singe cald? ")
(setq ras (if (read) (print "ciine") (print "lacusta")))
(print "ok? ")
(if (not (read))
(progn
(my-print (list "Puneti o intrebare la care " ras " sa
fie raspunsul pozitiv: "))
(setq int (read-line))
(print "Animalul pentru NIL: ") (setq aniF (string
(read)))
(modify animal (list 'print ras)
(list 'progn
(list 'print int)
(list 'if (list 'read) (list 'print ras) (list
'print aniF)))
)
))
))
(defun modify(str old new)
(cond ((or (atom str) (null str)) nil)
((equal (car str) old) (rplaca str new) T)
((modify (car str) old new) t)
(t (modify (cdr str) old new))))
(defun my-print (l)
(cond ((null l) (terpri))
(t (princ (car l)) (my-print (cdr l)))))

Ceea ce urmeaz este un dialog n care utilizatorul are n minte animalul


soprl:
> (eval animal)
"Animalul are singe cald? "nil
"lacusta"
"ok? "nil
Puneti o intrebare la care lacusta sa fie raspunsul pozitiv:
Animalul zboara?
"Animalul pentru NIL: "sopirla
T
> (eval animal)
"Animalul are singe cald? "nil
"Animalul zboara?"nil
"SOPIRLA"
"ok? "t
NIL

Bibliografie
Giumale, Cr., Preoescu, D., erbnai, L.D. 1987. LISP, vol. 1, Editura
Tehnic, Bucureti.
Tufi, D., Cristea, D., Tecuci, D. 1987. LISP, vol. 2, Editura Tehnic, Bucureti.
Graham, P., 1994. On Lisp. Advanced Techniques for Common Lisp. Prentice
Hall, Englewood Cliffs, New Jersey.
Steele, G. L., 1990. Common Lisp the Language, 2nd edition, Digital Press
Tufi D. 1987. TC-LISP-Funciile primitive ale interpretorului. Manual de
programare, ITCI, 98 p.
Tufi D., O. Popescu. 1987. TC-LISP-Biblioteca de funcii. Manual de
programare, ITCI, 101 p.

You might also like