Sunteți pe pagina 1din 57

Capitolul 2

2.1 Introducere

LISP

Intenia acestui capitol nu este aceea de a da o descriere exhaustiv a limbajului LISP ci de a introduce noiunile eseniale ale lui, 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 Inc. (www.franz.com). Common Lisp (Steele, 1990) este dialectul pe care l-a 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 limbajul 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, conceptul de prelucrare a datelor cunoscut sub numele de prelucrare simbolic. Fr a neglija calculul numeric, limbajele simbolice au faciliti speciale de a lucra cu simboluri nenumerice. Lisp este unul dintre limbajele care exemplific paradigma de programare funcional1. O funcie este un obiect matematic care ntoarce o unic valoare pentru un set de valori date n intrare. Toate construciile unui limbaj funcional pur sunt funcii care ntorc valori atunci cnd sunt evaluate. Lisp, ca i multe dintre suratele lui funcionale, a acceptat ns i efecte laterale n evaluarea funciilor, care dei l-au impurificat i-au mrit expresivitatea. 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. Lispul este adesea mai
1

Alturi de Erlang pentru aplicaii concurente, R pentru prelucrri statistice, Mathematica pentru matematic simbolic, APL cunoscut mai ales pentru facilitile de a opera cu matrici, extensia ulterioar a acestuia J, K pentru analiz financiar, XSLT pentru transformarea documentelor XML, ct i limbajele noi ML (al lui Robin Milner), ct i Miranda (David Turner) i urmaul acestuia Haskell (dezvoltat de Haskell Curry).

concis dect alte limbaje. Lisp este limbajul care se dezvolt pe msur ce rezolvai problema. Aceast trstur provine din utilizarea macrourilor secvene de cod ce suport execuii special. Prin macrouri se poate da nu numai o nou interpretare noiunii de evaluare a formelor limbajului, ci se poate schimba nsi sintaxa. 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 sunt: facilitatea de a lucra cu simboluri, lista ca structur fundamental de date, 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 faciliteaz o abordare multi-strat n programare. Implementrile orientate-obiect ale acestui limbaj i confer toate trsturile cunoscute ale paradigmei. 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 mai multe echipe. Aa cum se sugereaz n Figura 2.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 componente 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 cerinelor problemei i, eventual, chiar s le depeasc. 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. Aceast 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 sunt eseniale n rezolvarea 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 2.1: Crearea programelor bottom-up fa de top-down

2.2 Funcii, maniere de definire i apel


2.2.1 Transparena referenial. Spunem c o funcie este transparent referenial dac valoarea ntoars de ea depinde numai de parametrii pe care i-a primit n intrare. Astfel, dac scriem n limbajul 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, care este 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. 2.2.2 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. 2.2.3 De la notaia lambda la Lisp pur Lisp este unul dintre cele mai vechi limbaje de programare aflate nc n larg utilizare (numai FORTRAN este mai vechi dect el). Primele specificaii ale limbajului au fost elaborate de John McCarthy, n 1958 i publicate n 1960 (McCarthy, 1960), pe baza calculului- dezvoltat de Alonso Church (Church, 1941) i a unor dezvoltri precedente ce au aparinut n principal lui Herbert Simon i Allen Newell (Logic Theory Machine i General Problem Solver, programe dezvoltate n limbajul IPL inventat de 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 funciilor, 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 sunt 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 precum 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:
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 sunt transparente referenial, c funciile nu au efecte laterale i c din limbaj lipsesc asignrile.

2.2.4 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. 2.2.5 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 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).

2.3 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. 2.3.1 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 sunt 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: prin asignare, prin legare i prin intermediul listelor de proprieti. Dou dintre aceste moduri de a primi valori (asignarea i listele de proprieti) sunt caracteristice oricrui simbol Lisp. Ceea ce difereniaz un simbol lexical de o variabil sunt numai situaiile n care variabilele pot fi legate la valori. Constantele sunt 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 sunt funcii care au un mecanism de evaluare special, n doi pai: expandarea i evaluarea propriu-zis (prezentate n seciunea 2.6 Macro-uri). Formele speciale sunt funcii de sistem care au, n general, o sintax i un comportament aparte. 2.3.2 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, sunt 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 2.6.2 Despre apostroful-stnga). Un template funcioneaz ca un program care modific forma unui ir de obiecte Lisp; , virgulele sunt 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 sunt insensibile la forma caracterelor (minuscule sau majuscule). Intern ns, formele Lisp sunt reprezentate cu majuscule, de aceea formele rezultate n urma evalurilor sunt 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, sunt reprezentate ca rapoarte 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 sunt notate ca o secven: #c(<real>, <imaginar>), n care <real> i <imaginar> sunt 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: + - * / @ $ % ^ & _ = < > ~ .2 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@info.uaic.ro

2.3.2 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) list format din 3 atomi, dintre care primii doi nenumerici, iar al treilea numeric; (alpha (a) beta (b 1) gamma (c 1 2)) list 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.

Tradiional, listelor li se pot asocia notaii grafice care i au obria n prima implementare a Lisp-ului, realizat pe o main IBM 704 la M.I.T.
2

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

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.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:

car

cdr
pointer ctre restul listei (a b c) pointer ctre restul listei (b c) pointer ctre primul element al listei (c) c lista (c) e reprezentat de aceast celul cons restul listei (c) e lista vid

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

pointer ctre primul element al listei (b c) b

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

lista (b c) e reprezentat de aceast celul cons

Figura 1.2: Notaia grafic de list

Evident, prin construcii de acest fel pot fi reprezentate liste n liste, pe oricte niveluri. Figura 2.3 exemplific lista: (a (alpha beta) (b (beta (gamma delta)) c))

a alpha beta b beta gamma delta c

Figura 2.3: Un exemplu de list

2.3.3 Perechi i liste cu punct Celula cons n care att car-ul ct i cdr-ul indic s-expresii atomice poart numele de pereche cu punct, pentru c n notaia liniar cele dou elemente sunt reprezentate ntre paranteze, separate prin punct. Astfel celula cons din Figura 2.4a se noteaz: (a.b). Dac o list conine perechi cu punct o vom numi list cu punct. Reprezentarea din Figura 2.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.

b c

a.

b.

Figura 2.4: Liste cu perechi cu punct

2.3.4 Evaluarea expresiilor 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 sunt 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 dac este 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 sunt lsate neevaluate. Evaluate ori nu, argumentele apelului sunt 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 sunt 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. Forma setq este prima dintr-o serie de forme cu care ne vom ntlni mai departe i care sunt utilizate cu precdere pentru efectele lor laterale mai degrab dect pentru valorile ntoarse de ele.

evaluarea unei liste care are pe prima poziie o lambda-expresie va fi prezentat n seciunea 2.4.15 Lambda expresii; o list care are pe prima poziie un nume de macrodefiniie va fi prezentat n seciunea 2.6 Macro-uri; 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.

2.4 Funcii i forme Lisp


2.4.1 Notaii Am vorbit pn acum despre funcii i forme Lisp fr s precizm distincia dintre ele. Funciile Lisp-ului i evalueaz toate argumentele nainte de a le transfera corpului funciei. n afara funciilor, prin utilizarea macro-urilor (v. seciunea 2.6 Macro-uri), s-au creat forme speciale care aplic reguli specifice de evaluare a argumentelor. Limbajul Lisp numr o clas semnificativ de funcii i de forme predefinite. n prezentarea funciilor i a formelor 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 sunt urmtoarele: e s-expresie n atom numeric i numr ntreg c numr complex s atom nenumeric (simbolic) l list f funcional (nume sau definiie de funcie sau form). 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 sunt 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 sunt 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 form sau 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 2.5.8 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 orientat stnga: s e, 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 sunt 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 sunt 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. 2.4.2 Asignarea unei valori unui simbol ntr-un limbaj imperativ, o notaie de genul x:=y, unde x i y sunt variabile, este n general recunoscut ca avnd interpretarea: 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, 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 2.3.4 (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|s1 e1,,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|s1 e2, 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 sunt evaluate serial i apoi tuturor variabilelor (simbolurile de pe poziiile impare) le sunt 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],s1 e2 s2*k-1 e2*k
n exemplul urmtor valorile lui x i y sunt schimbate ntre ele: > (setq x a) A > (setq y b) B > (psetq x y y x) NIL > x B > y A

2.4.3 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

2.4.4 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 l) e1 (cdr l) e2

(car nil) (cdr nil)


> (car A > (car (ALPHA > (cdr (B C) > (cdr NIL > (cdr B

nil nil
(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 i desemneaz numrul de ordine, iar al doilea o list, din ea urmnd a se face selecia. Dac l (e0.((em.nil))) i 0nm, atunci: (nth n l) en Dac n>m, 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 n>m, 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)

2.4.5 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 argumentele ei sunt 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, reprezentarea numerelor ca liste face s nu existe limite ce pot fi atinse uor n operaiile cu acestea, de aceea apeluri ca cele de mai jos sunt posibile:
> (* 99999999999999999999999999999999999999999999999999999999 8888888888888888888888888888888888888888888888888888) 888888888888888888888888888888888888888888888888888799991111111 111111111111111111111111111111111111111111112

Pentru toate funciile aritmetice acioneaz urmtoarea regul de contaminare: n cazul n care argumentele unei funcii numerice nu sunt de acelai tip, toate sunt 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 (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 (ceiling n1 n2) c + 1, -r
>(floor 7 3) 2 1 >(ceiling 7 3)

3 -2

Exist dou forme Lisp de incrementare, respectiv, decrementare, fr efecte laterale: 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, respectiv, 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

Funciile max i min calculeaz maximumul i minimumul unor iruri 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, ce 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) (realpart c) n1 (imagpart c) n2 Funcia complex compune un numr complex dintr-o parte real i una imaginar: (complex n1 n2) #c(n1 n2)
> (conjugate (complex 1 2)) #c(1 -2) > (realpart (complex 2 3)) 2 > (imagpart (complex 2 3)) 3

2.4.6 Predicate Predicatele sunt funcii care ntorc valori de adevr. Multe dintre ele 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 i n0, atunci: (plusp n) t altfel: (plusp n) nil Dac n este numr i n0, 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 (boundp e) altfel: (boundp e)
> (setq x Z > (boundp T > (boundp T > (boundp T > (boundp NIL

s i s este un simbol legat, atunci: t nil


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

Dac e1 i e2 sunt 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 sunt reprezentate cu unicitate. Acest lucru face ca apelul de mai sus se evalueze 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 sunt 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 2.4.7 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. Funcionala lambda, utilizat n aceste exemple, este tratat n seciunea 2.4.15 Lambda expresii.

2.4.8 Operaii logice i evaluri controlate Operatorii logici n Lisp sunt and, or i not. Dintre acetia and i or, pentru c i evalueaz argumentele n mod condiionat, sunt considerai 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
> 7 > T > 0 > (setq n 7) (or (not (zerop n)) (/ 1 n)) (setq n 0) (or (not (zerop n)) (/ 1 n))

Error: Attempt to divide 1 by zero.

2.4.9 Forme pentru controlul evalurii Cele mai utilizate forme Lisp pentru controlul explicit al evalurii sunt 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: 1 (cond (e11 e1n ) (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"

Cu toat simplitatea ei, forma if are un dezavantaj, i anume faptul c nu permite evaluarea a mai mult dect o singur expresie nainte de ieire. Soluia o dau formele when i unless. Astfel, dac e1 ~nil, atunci: (when e1 e2 en) en|e2,,[en] altfel: (when e1 e2 en) nil Similar, dac e1 nil, atunci: (unless e1 e2 en) en|e2,,[en] altfel: (unless e1 e2 en) nil

2.4.10 Liste i tabele de asociaie Listele de asociaie sunt structuri frecvent folosite n Lisp pentru accesul rapid la o dat prin intermediul unei chei. Elementele unei liste de asociaie sunt 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 cheie-dat 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 sunt 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 'a) (list 1 2 3))) ((A . 3) (B . 2) (A . 1)) > (assoc 'a vals) (A . 3) > (rassoc 1 vals) (A . 1)

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. Astfel, 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 n care se transmit argumentele funcionalei: ca prim argument al funcionalei este considerat primul argument al funciei assoc sau rassoc, iar ca cel de al doilea argument pe rnd cte o cheie, respectiv, o dat din lista de asociaie.

2.4.11 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 pe care o primete ca prim argument, 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. 2.4.12 Lista de proprieti a unui simbol Unui simbol i se poate asocia o list de perechi proprietate-valoare, n care proprietile sunt 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 sunt 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. Astfel, 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)

2.4.13 Funcii chirurgicale Funciile chirurgicale i justific numele prin faptul c realizeaz modificri asupra argumentele. Ele sunt 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 din 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 E) > (rplaca x 'alpha) (ALPHA B C) > z (A B C D E) > (rplaca y 'beta) (BETA E) > z (A B C BETA E)

2.4.14 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

2.4.15 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) sk epk, ec1,, [ecn], unbind(s1,, sk)

ecn | s1 ep1,,

Proceduri uzuale de apel sunt: 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 (s1sk) se leag la valorile actuale evaluate (ep1epk), apoi formele corpului definiiei (ec1ecn) sunt evaluate una dup alta. Valoarea ntoars este cea rezultat din evaluarea ultimei forme. Lambda-definiia marcheaz un context (domeniu) lexical (discutat n seciunea 2.5.5 Variabile i domeniile lor) pentru variabilele locale.
> ((lambda(x y) (> x y)) 3 2) T

2.4.16 Lambda-funcii recursive3 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 (<specifica ie-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)
3

Exemplele din aceast seciune snt preluate din (Graham, 1994).

(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).

2.4.17 Funcii de coresponden Din aceast categorie fac parte funcii care aplic o funcional asupra argumentelor construite din listele primite ca parametri. Funciile de coresponden, exersate corect, formeaz deprinderea de a gndi transformri aplicate unei mulimi de obiecte, de o manier global, iar nu ca iterri asupra elementelor mulimii. Mapcar aplic o funcie asupra elementelor 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 2.5).
> (mapcar 'equal '(a (b c) d e) '(b (b c) f e 3)) (NIL T NIL T) Urmtorul apel realizeaz produsul scalar al doi vectori 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 (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) 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.
4 4

numrul argumentelor = aritatea func ionalei

(mapcar

f (f (f

l1 ln) e11 en1) e12 en2 ) e1 e2 ek


dimensiunea ieirii = lungimea listei minime

(f

e1k enk )

Figura 2.5: Evaluarea n cazul unui apel mapcar

numrul argumentelor = aritatea func ionalei

(maplist

#f

l1 l1 (cdr l1)

ln) ln (cdr ln) ) ) e1 e2 ek


dimensiunea ieirii = lungimea listei minime

(f (f

(f (cdr((cdr l1)) (cdr((cdr ln)) )


Figura 2.6: Evaluarea n cazul unui apel maplist

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:
> (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)

2.5 Tehnici de programare n Lisp


2.5.1 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"

2.5.2 Recursivitate n Lisp recursia este la ea acas. Iat definiia funciei factorial:
> (defun fact (n) (if (zerop n) 1 (* n (fact (1- n)))))

O strategie, care nu d gre, de definire a funciilor recursive zice aa: - 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 coad5 Se spune c o funcie 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) (car lst) (our-find-if fn (cdr lst))))

pentru c funcia ntoarce direct valoarea apelului recursiv. Urmtoarele funcii nu sunt 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 (suma1 (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
5

O parte din exemplele acestei seciuni snt preluate din (Graham, 1994).

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)))

2.5.3 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 sunt importante numai prin efectele lor laterale. 2.5.4 Formele speciale let i let* 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 (despre domenii vom vorbi pe larg n seciunea urmtoare). Cel mai frecvent tip de apel al formei let are forma: (let ((s1 ei1)(sn ein)) ec1eck) eck | ei1,,ein, s1 ei1 sn ein,ec1,,[eck],unbind(s1,,sn)

n acest apel, s1sn sunt variabile locale, ei1ein sunt expresii care, evaluate, configureaz valorile iniiale pe care le iau variabilele nainte de evaluarea n secven a expresiilor ec1eck. 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 ec1eck 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 sunt ns uitate la ieirea din form. ntradevr, 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 legarea variabilelor la valori este fcut serial. Cel mai frecvent tip de apel al formei let* are forma: (let* ((s1 ei1) (sn ein)) ec1eck) eck | ei1,s1 ei1, , ein,sn ein,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

2.5.5 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 2.3 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. Aici, simbolului care d numele variabilei i se poate asocia o valoare. Un numr de fome ale Lisp-ului (printre ele: defun, lambda, let, let*, do, dolist, dotimes etc., adic acele forme ce permit definirea de parametri locali) creeaz domenii (sau ntinderi) ale variabilelor ce apar pe post de parametri locali n aceste forme. Un domeniu este un spaiu lexical contiguu, mrginit de perechea de paranteze care mbrac o form, ce 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 2.7 sunt 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 sunt: 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 sunt referite variabilele: x cu valoarea legat n contextul A i v cu valoare global. n sfrit, n domeniul C sunt 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,v
B

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

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

Figura 2.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 et al., 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, 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.

2.5.6 Legri versus asignri 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. Exemplul urmtor lmurete:
> (let ABA A > (let x)) ABB B > (let (prin1 ACA A ((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))

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, n exemplul urmtor, 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 2.4.2 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 (v. seciunea 2.4.13 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). 2.5.7 Forme de iterare dolist itereaz aceleai evaluri asupra tuturor elementelor unei liste. Dac l = (e1.((ek.nil))), atunci: (dolist (s l e) ec1 ecn) ec1,,ecn,[e],unbind(s)
> (dolist (x '(a b c d) 'exit) (prin1 x) (princ " ") ) A B C D EXIT

e | s e1,ec1,,ecn,,s ek,

ntr-o alt form a apelului, valoarea ntoars poate fi ignorat: (dolist (s l) ec1 ecn) ec1,,ecn,[nil],unbind(s) nil | s e1,ec1,,ecn,,s ek,

dotimes itereaz aceleai evaluri de un numr anumit de ori: Dac n>0, atunci: (dotimes (s n e) ec1ecn) e | s 0,ec1,,ecn,, s n-1,ec1,,ecn,s n,[e],unbind(s)
> (dotimes (x 4 x) (prin1 x) (princ " ") ) 0 1 2 3 4 NIL

ntr-o alt form a apelului, valoarea ntoars poate fi ignorat: (dotimes (s n) ec1ecn) nil | s 0,ec1,,ecn,, s n-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 controlarea valorilor lor 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,s1 ei1 sn ein, while(not et) {ec1,,ecq,es1,,esn,s1 es1 sn esn},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 considerat nil. Dac expresia de incrementare este omis, variabila nu va fi schimbat ntre paii consecutivi ai iteraiei (dei corpul lui do poate modifica valorile variabilei prin setq). nainte de prima iteraie, toate formele de iniializare sunt evaluate i fiecare variabil este legat la valoarea de iniializare corespunztoare (acestea sunt legri iar nu asignri, 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 primei, variabilele sunt actualizate astfel: toate formele de incrementare sunt evaluate de la stnga la dreapta i rezultatele reprezint valorile la care sunt legate variabilele n paralel. n cazul formei do*, evaluarea formelor de iniializare urmat de legarea variabilelor la aceste valori, att la iniializare ct i la fiecare ciclu al iteraiei, se efectueaz serial: (do* ((s1 ei1 es1) (sn ein esn)) (et er1 erp) ec1 ecq) erp | ei1,s1 ei1,,ein,sn ein,while(not et){ec1,, ecq,es1,s1 es1,,esn,sn esn},er1,,[erp],unbind(s1,, sn) index: Exemplele urmtoare exploateaz legrile paralele ale variabilelor de
(defun list-reverse(lst) (do ((x lst (cdr x)) (y '() (cons (car x) y))) ((endp x) y))) Funcia list-reverse , realizeaz inversarea unei liste. Variabila de ciclu x se leag la liste din ce n ce mai scurte din cea iniial, lst, n timp ce variabila y pleac de la lista vid i adaug la fiecare iteraie primul element al listei x. Cnd x ajunge la lista vid do se termin ntorcnd valoarea acumulat n y. (defun rev (lst)
6

Exemplu preluat din (Steele, 1990).

(cond ((null lst) nil) ((null (cdr lst)) lst) (t (do ((z (cddr lst) (cdr z)) (y (cdr lst) z) (x (progn() (rplacd lst nil) lst) y)) ((null z) (rplacd y x) y) (rplacd y x))))) Funcia rev definit aici realizeaz, de asemenea, inversa unei liste, dar, spre deosebire de exemplul precedent, nu se consum alte celule de memorie fa de cele n care era memorat lista dat n intrare. Cu alte cuvinte, lista se inverseaz n ea nsi. Primele dou clauze cond trateaz lista vid i lista format dintr-un singur element. Orice list care are minimum dou elemente este preluat de do. Variabilele x, y i z in minte la fiecare ciclu trei celule cons aflate n secven n lista iniial. La iniializare, cdr-ul celulei de pe prima poziie (indicat de x) este fcut nil pentru a realiza sfritul de list. Operaia efectiv a fiecrui pas, realizat pe ultimul rnd al definiiei de funcie, const n modificarea prii cdr a elementului din mijloc (y) pentru a indica elementul precedent (x). Dup realizarea operaiei din ciclu, variabilele i transfer una alteia valorile n paralel mutndu-se toate cu cte un element mai spre sfritul listei iniiale. Cnd ultima variabil (z) devine nil nseamn c y indic ultimul element al vechii liste, ca urmare nsi cdr-ul acesteia este modificat la elementul precedent i se ntoarce valoarea indicat de ea, care reprezint acum captul listei inversate.

2.5.8 Valori multiple i exploatarea lor Dup cum s-a putut vedea, o seam de funcii, printre care floor i ceiling, ntorc valori multiple. O generare explicit de valori multiple se poate face cu formele values-list i values:
> (values-list (list 'a 'b 3 'c)) A B 3 C > (values 'a 'b 3 'c) A B 3 C

Diferena dintre ele este c prima solicit o list pe cnd a dou primete valorile pe care le multiplic ca simple argumente. 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 sunt inverse una alteie:


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

-2

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 multiple-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 sunt valorificate. Ultima form evaluat genereaz valoarea/valorile ntoars/ntoarse de macro.
> (multiple-value-bind (x y) (ceiling 7 3) (list x y)) (3 -2)

2.5.9 nchideri7 Combinaia dintre o funcie i un set de legri de variabile libere ale funciei la momentul apelului acelei funcii se numete nchidere (closure). nchiderile sunt funcii mpreun cu stri locale. n exemplul urmtor, se definete o nchidere la nivelul apelului lui mapcar, pentru care n este o variabil liber. Ea dispare la nivelul definiiei funciei list+:
> (defun list+ (lst n) (mapcar #'(lambda (x) (+ x n)) lst)) > (list+ '(1 2 3) 10) (11 12 13)

Urmtoarele funcii folosesc mpreun 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)
7

Exemplele din aceast seciune snt preluate din (Graham, 1994).

#'(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

2.5.10 Transferul argumentelor n funcii n transferul prin valoare (Figura 2.8), 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: x:

v1 v2

n momentul apelului n timpul evalurii lui F

Figura 2.8: Transferul prin valoare, n general

n transferul prin referin (Figura 2.9) 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:

F(a)

a:

v1

F(x) x := v2

x:a: x:a:

v1 v2

n momentul apelului n timpul evalurii lui F

Figura 2.9: Transferul prin referin, n general

Transferul n Lisp nu se face nici prin valoare nici prin referin, dar ambele tipuri pot fi simulate. n Lisp un parametru formal se leag la valoarea comunicat prin parametrul 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 (Figura 2.10).
F(a) a v1

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

x x v2

n momentul apelului n timpul evalurii lui F

Figura 2.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 (Figura 2.11) pentru c n acest mod parametrul actual simte transformrile structurii accesate temporar prin parametrul formal.
F(a) a v1 a v2

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

n momentul apelului

n timpul evalurii lui F

Figura 2.11: Transferul prin referin n Lisp

2.6 Macro-uri
2.6.1 Definiie, macroexpandare i evaluare Un macro este n esen o funcie care definete o funcie. Macro-urile sunt 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 sunt 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 sunt 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 sunt 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. 2.6.2 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 sunt 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. 2.6.3 Asupra manierei de construcie a macro-urilor8 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 cte un nume pentru fiecare argument. ntre rndul apelului i cel al expandrii se traseaz sgei ntre argumentele corespunztoare (Figura 2.12). (memq x choices)
apelul

(member x choices :test #eq)

expandarea

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


Figura 2.12: Reguli de dezvoltare a macro-urilor

definiia

Cazul unui macro cu un numr oarecare de argumente vom ncepe, la fel, prin a scrie un eantion de apel de macro. Plecnd de la acesta, construim lista de parametri ai macrou-lui, dar n care, n afar de parametrul care realizeaz testul, vom descrie corpul macro-ului printr-un parametru &rest sau &body. 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.
8

Exemplele din aceast sec iune i urmtoarele sunt reproduse din (Graham, 1994).

Apel:
(my-while (not running-engine) (look-at-engine) (or (ask-advice) (think)) (try-to-fix-the-motor) (turn-on-the-key)) (defmacro my-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))

2.6.4 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

2.6.5 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 macrourilor. 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

2.6.6 Cnd s folosim macro-uri? Exist buci de cod care pot fi scrise att ca macro-uri ct i ca funcii. De exemplu:
(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. 2.6.7 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

2.7 Un exemplu de program care se modific n timpul rulrii


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 bogat. n acest fel, prin rulri repetate, cunoaterea programului asupra diferitelor animale se perfecioneaz.
(setq animal '(let ((int)(aniF)) (print "Animalul are sange cald? ") (setq ras (if (read) (print "caine") (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 oprl:
> (eval animal) "Animalul are sange cald? "nil "lacusta" "ok? "nil Puneti o intrebare la care lacusta sa fie raspunsul pozitiv: Animalul zboara? "Animalul pentru NIL: "soparla T > (eval animal) "Animalul are sange cald? "nil "Animalul zboara?"nil "SOPARLA" "ok? "t NIL

Cerine pentru studeni S fie capabili s implementeze algoritmi n LISP prin funcii i macro-uri. S poat citi o expresie LISP. Probleme
P2.1 a). S se scrie o funcie LISP care s creeze structura din Figura 2.13a. b). S se scrie o funcie care modific aceast structur n cea din Figura 2.13b. c). Ce va ntoarce (eq (car (cdr (caddr X))) (car (cdddr X)))?

Z Y

W Zx

a. Figura 2.13: a. Structura iniial. b. Structura transformat

b.

P2.2 a). S se construiasc structur de celule cons din Figura 2.14a. b). S se modifice apoi la forma din Figura 2.14b. c). La ce se evalueaz (eq (car x) (cadr x)) n primul caz; i n cel de al doilea caz.
x x

beta beta

a. b. Figura 2.14: a. Structura iniial. b. Structura transformat P2.3 S se explice ce ntoarce urmtorul program Lisp:
(defun boo(foo) (cond ((null foo) 0) (t (+ 1 (boo (cdr foo)))))) (boo (alpha beta gamma))

P2.4 S se transforme urmtoarea funcie recursiv ntr-una iterativ:


(defun boo-iter(foo) (let ((temp 0)) (while foo (setq temp (+ 1 temp) foo (cdr foo)))))

P2.5 Fie X o mulime de elemente ordonate cresctor, X=(x1, x2 xn), reprezentnd puncte pe o ax, i fie f(x) o funcie. Scriei diverse variante de funcii LISP care s calculeze mulimea Y=(f(x1), f(x2) ... f(xn)).

Bibliografie Church, A., 1941. The Calculi of Lambda-Conversion, Princeton University Press, Princeton, N. J. Giumale, Cr., Preoescu, D., Tehnic, Bucureti. erbnai, L.D. 1987. LISP, vol. 1, Editura

Graham, P., 1994. On Lisp. Advanced Techniques for Common Lisp. Prentice Hall, Englewood Cliffs, New Jersey. McCarthy, J. 1960. Recursive Functions of Symbolic Expressions and Their Computation by Machine, in Communications of the ACM (poate fi accesat la http://www-formal.stanford.edu/jmc/recursive.pdf).

Steele, G. L., 1990. Common Lisp the Language, 2nd edition, Digital Press (versiune on-line la http://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html). Truan-Matu, . 2004. Programare n LISP. Inteligen Artificial i web semantic. Editura Polirom, Iai, 2004 Tufi D. 1987. TC-LISP-Funciile primitive ale interpretorului. Manual de programare, ITCI, 98 p. Tufi, D., Cristea, D., Tecuci, D. 1987. LISP, vol. 2, Editura Tehnic, Bucureti. Tufi D., O. Popescu. 1987. TC-LISP-Biblioteca de funcii. Manual de programare, ITCI, 101 p. Situri unde gsii documentaie, cursuri i produse: http://www.franz.com/ situl companiei Franz Inc. care produce i comercializeaz Allegro Common Lisp. n seciunea Free Downloads putei gsi ultimele versiuni de Common Lisp (CL 8.0 n februarie 2007). Allegro CL Certification Program la http://www.franz.com/services/classes/ ofer gratis cursuri on-line n vederea obinerii certificatelor de dezvoltator CL.

S-ar putea să vă placă și