Copyright © Peter Seibel
Traducere © Razvan Popa
7. Macrouri: Constructii Standard de Control
In timp ce multe alte idei originale din Lisp, de la expresia conditionala la colectarea gunoiului (garbage collection), au fost incorporate in alte limbaje, exista o functionalitate ce continua sa diferentieze Common Lisp, si anume sistemul sau de macrouri. Din pacate, in lumea calculatoarelor cuvantul macro descrie multe lucruri care seamana doar pe departe cu macrourile din Common Lisp. De aici rezulta multe neintelegeri atunci cand programatorii in Lisp incearca sa explice celorlalti ce tari sunt macrourile.1 Pentru a intelege macrourile din Lisp trebuie sa le abordezi cu minte limpede, fara preconceptii bazate pe alte lucruri care se intampla sa fie numite tot macrouri. Asa ca hai sa incepem discutia noastra despre macrourile din Lisp facand un pas inapoi pentru a vedea diversele moduri in care limbajele de programare pot fi extinse.Orice programator ar trebui sa fie obisnuit cu ideea ca definitia unui limbaj poate include o biblioteca standard de functionalitate care este implementata in termeni de "nucleu" al limbajului -- functionalitate care ar fi putut fi implementata de orice programator peste limbaj daca nu ar fi facut parte deja din biblioteca standard. Biblioteca standard a C-ului, de exemplu, poate fi implementata aproape in intregime in C portabil. Similar, cea mai mare parte din multimea de clase si interfete din Kit-ul de Dezvoltare Java (JDK) standard sunt scrise in Java "pur".
Un avantaj al definirii limbajelor in termeni de nucleu plus o biblioteca standard este ca le face mai usor de inteles si implementat. Dar beneficiul cel mai de seama este in privinta expresivitatii -- deoarece mare parte din ceea ce este considerat "nucleu" este de fapt o biblioteca -- limbajul este usor de extins. Daca C nu are o anumita functie pentru a face ceva ce ai tu nevoie, poti sa scrii acea functie, si apoi ai o versiune un pic mai bogata de C. Similar, intr-un limbaj ca Java sau Smalltalk in care aproape toate partile interesante ale "limbajului" sunt definite in termeni de clase, prin definirea de noi clase extinzi limbajul, facandu-l mai potrivit pentru a scrie programele pe care vrei sa le scrii.
In timp ce Common Lisp suporta ambele aceste metode de extindere a limbajului, macrourile ii dau o a treia cale. Dupa cum am discutat pe scurt in Capitolul 4, fiecare macro isi defineste propria sintaxa, determinand modul in care expresiile S care ii sunt transmise sunt transformate in forme Lisp. Avand macrourile incluse in nucleul limbajului este posibil sa se construiasca elemente noi de sintaxa -- constructii de control precum
WHEN
, DOLIST
si LOOP
precum si forme definitionale ca DEFUN
si DEFPARAMETER
-- ca parte a "bibliotecii standard" in loc sa trebuiasca sa fie indesate in nucleu. Lucrul acesta are implicatii asupra modului in care limbajul insusi este implementat, dar in calitate de programator Lisp iti va pasa mai mult de faptul ca primesti o noua metoda de a extinde limbajul, facandu-l mai bun pentru exprimarea de solutii la problemele tale concrete.Acum, s-ar zice ca beneficiile unui mod nou de a extinde limbajul ar trebui sa fie usor de recunoscut. Dar din diverse motive multi dintre cei care nu au folosit propriu-zis macrouri Lisp -- oameni care nu au nimic impotriva petrecerii timpului pentru a crea noi abstractiuni functionale sau pentru a defini ierarhii de clase ca sa-si rezolve problemele lor de programare -- se sperie de ideea de a fi capabil sa definesti noi abstractiuni sintactice. Cauza cea mai des intalnita cauza a macrofobiei pare sa fie legata de experiente neplacute cu celelalte sisteme de "macrouri". In plus, frica de necunoscut joaca un si ea un rol. Pentru a evita declansarea reactiilor macrofobice voi intra mai usor in subiect, discutand cateva din macrourile standard de control definite de Common Lisp. Acestea ar fi trebuit sa fie adaugate in nucleul limbajului, daca Lisp n-ar fi avut macrouri. Cand le folosesti, nu trebuie sa-ti pese ca sunt implementate ca macrouri, totusi le poti privi ca exemple bune de lucruri pe care le poti face cu macrourile.2 In capitolul urmator iti voi arata cum sa iti definesti propriile macrouri.
WHEN si UNLESS
Dupa cum ai vazut deja, forma cea mai de baza de executie conditionala -- daca x, fa y; altfel fa z -- este pusa la dispozitie de operatorul special
IF
, al carei forma de baza este:(if conditie forma-atunci [forma-altfel])Conditia este evaluata si daca valoarea ei este non-
NIL
, forma-atunci este evaluata si valoarea care rezulta e returnata. Altfel, forma-altfel, daca este prezenta, e evaluata si valoarea sa returnata. Daca conditie este NIL
si nu exista forma-atunci, IF
returneaza NIL
.(if (> 2 3) "Yup" "Nope") ==> "Nope" (if (> 2 3) "Yup") ==> NIL (if (> 3 2) "Yup" "Nope") ==> "Yup"Cu toate acestea,
IF
nu este de fapt o constructie sintactica atat de tare pentru ca forma-atunci si forma-altfel sunt fiecare restrictionate la a fi o singura forma Lisp. Asta inseamna ca daca vrei sa executi o secventa de actiuni in oricare din clauze trebuie sa o incluzi intr-un alt fel de sintaxa. De exemplu, sa zicem ca intr-un program de filtrare a SPAM-ului ai vrea sa marchezi un fisier ca SPAM si in acelasi timp si sa actualizezi baza de date SPAM atunci cand un mesaj este SPAM. Nu poti sa scrii asa: (if (spam-p current-message) (file-in-spam-folder current-message) (update-spam-database current-message))pentru ca apelul la
update-spam-database
va fi tratat ca si clauza altfel, nu ca si parte din clauza atunci. Un alt operator special, PROGN
, executa orice numar de forme in ordine si returneaza valoarea ultimei forme. Deci programul de mai sus ar putea fi scris astfel:(if (spam-p current-message) (progn (file-in-spam-folder current-message) (update-spam-database current-message)))Nu e prea urat. Dar tinand cont ca vei folosi acest idiom de multe ori, nu e greu sa-ti imaginezi ca te-ai satura de el dupa o vreme. Te-ai intreba "De ce nu are Lisp o modalitate de a exprima ce vreau eu de fapt, adica 'Atunci cand x e adevarat, fa asta, astalalta si inca ceva?" Cu alte cuvinte, dupa o vreme ai observa un model care are un
IF
plus un PROGN
si ti-ai dori o modalitate de a abstractiza detaliile astea in loc sa trebuiasca sa le scrii explicit de fiecare data.Macrourile ajuta exact in aceste conditii. In cazul de fata, Common Lisp pune la dispozitie un macro standard,
WHEN
, care te lasa sa scrii astfel: (when (spam-p current-message) (file-in-spam-folder current-message) (update-spam-database current-message))Dar daca nu era inclus in biblioteca standard, ai putea defini tu
WHEN
cu un macro in genul asta, folosind citarea cu ` (backquote notation) despre care am discutat in Capitolul 3:3(defmacro when (condition &rest body) `(if ,condition (progn ,@body)))Un complement al macroului
WHEN
este UNLESS
, care inverseaza conditia, evaluand formele din corp numai cand conditia este falsa. Cu alte cuvinte: (defmacro unless (condition &rest body) `(if (not ,condition) (progn ,@body)))Ce e drept, acestea sunt macrouri destul de triviale. Nu e nici o vraja aici, doar cat sunt abstrase cateva detalii necesare limbajului, permitandu-ti sa iti exprimi intentia reala ceva mai clar. Dar simpla lor trivialitate subliniaza un lucru important: datorita faptului ca sistemul de macrouri este integrat in limbaj, poti sa scrii macrouri triviale precum
WHEN
si UNLESS
care iti confera castiguri mici dar reale in claritate, care apoi sunt multiplicate atunci cand le folosesti in cod. In Capitolele 24, 26 si 31 vei vedea moduri in care macrourile pot fi folosite si la scara mai larga pentru a crea intregi limbaje specifice unui domeniu (DSL - domain specific language). Dar mai intai sa terminam cu discutia despre macrourile de control standard.COND
Un alt moment in care expresiile
IF
simple pot ajunge urat este acela in care ai nevoie de conditii cu mai multe ramuri: daca a fa x, altfel daca b fa y; altfel fa z. Nu e o problema logica scrierea de astfel de lanturi de expresii conditionale folosind doar IF
, dar nu e frumos.(if a (do-x) (if b (do-y) (do-z)))Si ar fi si mai rau daca ai avea nevoie sa incluzi forme multiple in clauzele "atunci", necesitand
PROGN
-uri. Asa ca nu e o surpriza faptul ca Common Lisp pune la dispozitie un macro pentru exprimarea conditionalelor multi-ramura: COND
. Asa arata scheletul de baza:(cond (test-1 form*) . . . (test-N form*))Fiecare element al corpului reprezinta o ramura a constructiei conditionale si consta intr-o lista continand o forma drept conditie si zero sau mai multe forme care trebuie evaluate daca ramura respectiva este aleasa. Conditiile sunt evaluate in ordinea in care apar ramurile in corp pana ce una din ele este evaluata ca adevarata. In acel moment, formele urmatoare din acea ramura sunt evaluate, si valoarea ultimei forme din ramura este returnata ca si valoare a lui
COND
per ansamblu. Daca ramura nu contine forme dupa conditie, se returneaza valoarea conditiei. Prin conventie, ramura reprezentand clauza "atunci" finala intr-un lant daca/atunci-daca (if/else-if) se scrie ca avand conditia T
. Orice valoare non-NIL
functioneaza, dar un T
serveste ca reper la citirea. Asadar, poti scrie expresia imbricata IF
de dinainte folosind COND
astfel: (cond (a (do-x)) (b (do-y)) (t (do-z)))
AND, OR si NOT
Cand scrii conditiile din formele
IF
, WHEN
, UNLESS
si COND
, operatorii logici AND
, OR
si NOT
vor fi de mare ajutor.NOT
este o functie asa ca, strict vorbind, nu apartine acestui capitol, dar este legata strans de AND
si OR
. Primeste un singur argument si ii inverseaza valoarea de adevar, returnand T
daca argumentul este NIL
, altfel NIL
.AND
si OR
, in schimb, sunt macrouri. Ei implementeaza conjunctia si disjunctia logice ale unui numar oarecare de subforme si sunt definite ca macrouri pentru a putea scurtcircuita. Asta inseamna ca evalueaza cel mai mic numar de subforme -- de la stanga la dreapta -- necesar pentru a determina valoarea de adevar a formei totale. Prin urmare AND
se opreste si returneaza NIL
de indata ce una din subforme este evaluata ca NIL
. Daca toate subformele sunt evaluate ca fiind non-NIL
, este returnata valoarea ultimei subforme. OR
, pe de alta parte, opreste evaluarea de indata ce una din subforme este evaluata ca non-NIL
si returneaza valoarea rezultata. Daca nici una din subforme nu este evaluata ca adevarata, OR
returneaza NIL
. Urmeaza cateva exemple:(not nil) ==> T (not (= 1 1)) ==> NIL (and (= 1 2) (= 3 3)) ==> NIL (or (= 1 2) (= 3 3)) ==> T
Buclele
Constructiile de control sunt celalalt tip de constructii de buclare. Constructiile de buclare din Common Lisp -- pe langa ca sunt destul de puternice si flexibile -- sunt o lectie interesanta in stilul de programare mananca-dar-si-pastreaza-prajitura pe care-l pun la dispozitie macrourile.
Dupa cum se va vedea, nici unul din cei 25 de operatori speciali din Lisp nu suporta buclarea stricturata. Toate constructiile de buclare din Lisp sunt macrouri create pe baza unei perechi de operatori speciali care faciliteaza un fel de goto primitiv.4 La fel ca multe abstractiuni, sintactice sau nu, macrourile de buclare din Lisp sunt construite intr-un set pe niveluri incepand de la cei doi operatori speciali.
La baza (lasand la o parte operatorii speciali) este o constructie de buclare foarte generala,
DO
. Desi foarte puternic, DO
sufera, la fel ca multe abstractiuni generale, din cauza faptului ca este prea complicat pentru situatii simple. Asa ca Lisp pune la dispozitie si alte doua macrouri, DOLIST
si DOTIMES
, mai putin flexibile decat DO
dar care sunt suficiente pentru cazurile comune, cand se doreste parcurgerea elementelor unei liste sau buclarea cu numarare. O implementare poate crea aceste macrouri in orice fel, dar de obicei ele sunt expandate intr-o bucla echivalenta DO
. Deci DO
este o constructie de buclare pe baza primitivelor date de operatorii speciali din Common Lisp, iar DOLIST
si DOTIMES
pun la dispozitie doua constructii mai usor de folosit, chiar daca mai putin generale. Si, dupa cum vei vedea in capitolul urmator, iti poti face propriile mecanisme de buclare peste DO
pentru situatiile in care DOLIST
si DOTIMES
nu sunt suficiente.In final, macroul
LOOP
da un mini-limbaj complet pentru exprimarea constructiilor de buclare intr-un limbaj non-Lisp, gen-Engleza (sau macar gen-Algol). Unii hackeri Lisp iubesc LOOP
; altii il urasc. Fanii il plac pentru ca este un mod concis de a exprima anumite constructii de buclare des folosite. Detractorii il displac pentru nu e destul de Lispy. Oricum ai lua-o, este un exemplu remarcabil de putere a macrourilor de a adauga constructii noi in limbaj. DOLIST si DOTIMES
Voi incepe cu macrourile
DOLIST
si DOTIMES
cele-usor-de-folosit.DOLIST
cicleaza prin elementele unei liste, executand corpul buclei in timp ce tine intr-o variabila elementele succesive din lista.5 Acesta este scheletul de baza (omitand cateva optiuni esoterice):(dolist (var list-form) body-form*)La inceputul buclei forma-lista este evaluata o data pentru a produce o lista. Apoi corpul buclei este evaluat cate o data pentru fiecare element din lista cu variabila var stocand valoarea elementului. De exemplu:
CL-USER> (dolist (x '(1 2 3)) (print x)) 1 2 3 NILFolosita astfel, forma
DOLIST
per ansamblu este evaluata ca NIL
.Daca vrei sa iesi dintr-o bucla
DOLIST
inainte de terminarea listei, poti folosi RETURN
.CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return))) 1 2 NIL
DOTIMES
este constructia de nivel inalt pentru buclele cu numarare. Modelul de baza este foarte asemanator cu DOLIST
. (dotimes (var count-form) body-form*)Count-form trebuie sa returneze un numar intreg. La fiecare trecere prin bucla var tine numere intregi succesive de la 0 pana la numarul anterior celui returnat de count-form. De exemplu:
CL-USER> (dotimes (i 4) (print i)) 0 1 2 3 NILLa fel ca la
DOLIST
, poti folosi RETURN
pentru a iesi dintr-o bucla mai devreme.Datorita faptului ca in corpul lui
DOLIST
si DOTIMES
pot fi puse orice tip de expresii, poti sa folosi si bucle imbricate. De exemplu, pentru a tipari tabela de inmultire de la 1 * 1 = 1
la 20 * 20 = 400
, poti scrie urmatoarea pereche de bucle DOTIMES
imbricate:(dotimes (x 20) (dotimes (y 20) (format t "~3d " (* (1+ x) (1+ y)))) (format t "~%"))
DO
DOLIST
si DOTIMES
sunt convenabile si usor de folosit, dar nu sunt destul de flexibile pentru a fi folosite pentru toate buclele. De exemplu, cum faci daca vrei sa ciclezi folosind mai multe variabile in paralel? Sau sa folosesti o expresie arbitrara pentru a testa terminarea buclei? Daca nici DOLIST
nici DOTIMES
nu iti satisfac nevoile, tot ai acces la bucla DO
, mai generala.DOLIST
si DOTIMES
iti dau doar cate o variabila de ciclare, dar DO
te lasa sa legi orice numar de variabile si iti da control complet asupra modificarii lor in fiecare pas din bucla. Poti sa definesti si testul care determina cand se opreste buclarea si poti indica o forma care sa fie evaluata la final si a carei valoare sa fie returnata din expresia DO
per total. Modelul de baza arata astfel: (do (variable-definition*) (end-test-form result-form*) statement*)Fiecare variable-definition introduce o variabila care va fi accesabila in corpul buclei. Forma completa de definire a unei variabile este o lista cu trei elemente.
(var init-form step-form)init-form va fi evaluata la inceputul buclei si rezultatul va fi atribui variabilei var. Inaintea oricarei iteratii ulterioare ale buclei, step-form va fi evaluata si noua valoare atribuita var. step-form este optionala; daca lipseste, variabila isi va pastra valoare de la o iteratie la alta, in afara cazului in care ii atribui explicit o valoare noua in corpul buclei. La fel cu definitiile de variabile dintr-un
LET
, daca init-form lipseste, variabila este legata la NIL
. La fel, poti folosi un nume de variabila ca scurtatura pentru o lista care ar contine doar numele variabilei.La inceputul fiecarei iteratii, dupa ce toate variabilele din bucla au primit valorile noi, se evalueaza end-test-form. Atata timp cat este evaluata ca
NIL
, iteratia continua, evaluand statements in ordine.Cand end-test-form este evaluata ca adevarata, result-forms sunt evaluate si valoarea ultimului rezultat este returnat ca valoarea expresiei
DO
.La fiecare pas al iteratiei formele step-form pentru toate variabilele sunt evaluate inainte de atribuirea oricarei valori variabilelor. Asta inseamna ca te poti referi la oricare variabila din bucla in formele pas.6 Adica, intr-o bucla in genul:
(do ((n 0 (1+ n)) (cur 0 next) (next 1 (+ cur next))) ((= 10 n) cur))formele pas
(1+ n)
, next
si (+ cur next)
sunt toate evaluate folosind valorile vechi ale n
, cur
si next
. Numai dupa ce au fost evaluate toate formele pas, variabilele primesc noile valori. (Cititorii pasionati de matematica ar putea observa ca exemplul este o forma eficienta de a calcula al unsprezecilea numar Fibonacci.)Exemplul ilustreaza si alta caracteristica a lui
DO
-- datorita faptului ca poti folosi mai multe variabile, de multe ori nici nu e nevoie de corp. In alte cazuri poate sa lipseasca forma rezultat, mai ales daca folosesti bucla pur si simplu ca o constructie de control. Flexibilitatea aceasta, totusi, este motivul pentru care expresiile DO
pot fi un pic criptice. Unde trebuie pusa fiecare paranteza? Cel mai bun mod de a intelegi expresia DO
este sa tinem in minte modelul de baza.(do (variable-definition*) (end-test-form result-form*) statement*)Cele sase paranteze din model sunt singurele necesare pentru
DO
. E nevoie de o pereche pentru a incadra declaratiile de variabile, o pereche pentru testul de final si formele rezultat, si o pereche pentru a incadra toata expresia. ALte forme in interiorul DO
pot avea nevoie de parantezele proprii -- de exemplu, definitiile de variabile sunt de obicei liste. Si forma test este de obicei un apel de functie. Dar scheletul unei bucle DO
va fi intotdeauna la fel. Urmeaza cateva exemple de bucle DO
cu scheletul cu caractere ingrosate: (do ((i 0 (1+ i))) ((>= i 4)) (print i))De observat ca forma rezultat a fost omisa. Totusi, asta nu e o modalitate idiomatica de folosire a lui
DO
, intrucat bucla poate fi scrisa mai simplu cu DOTIMES
.7(dotimes (i 4) (print i))Ca alt exemplu, urmeaza bucla de calcul Fibonacci, fara corp:
(do ((n 0 (1+ n)) (cur 0 next) (next 1 (+ cur next))) ((= 10 n) cur))In final, bucla urmatoare demonstreaza o bucla
DO
care nu leaga nici o variabila. In ea se itereaza cat timp timpul curent este mai mic decat valoarea unei variabile globale, printand "Waiting" o data pe minut. De observat ca este nevoie de lista de variabile, chiar daca este goala.(do () ((> (get-universal-time) *some-future-date*)) (format t "Waiting~%") (sleep 60))
Puternicul LOOP
Pentru cazurile simple exista
DOLIST
si DOTIMES
. Si daca nu sunt suficiente, poti folosi DO
. Ce altceva ai putea dori?Ei bine, se pare ca o suma de modele de bucle tot apar in folosirea normala, cum ar fi ciclarea prin diferite structuri: liste, vectori, tabele de dispersie si pachete. Sau acumularea de valori in diverse moduri in timpul ciclarii: colectare, numarare, insumare, aflarea valorii minime sau maxime. Daca ai nevoie de o bucla care sa faca unul din aceste lucruri (sau mai multe in acelasi timp), macroul
LOOP
ar putea fi un mod mai usor de a o exprima.Macroul
LOOP
vine in doua arome -- simplu si extins. Versiunea simpla este pe cat de simpla posibil -- o bucla infinita care nu leaga nici o variabila. Bucla schelet arata astfel: (loop body-form*)Formele din corp sunt evaluate la fiecare trecere prin bucla; aceasta cicleaza infinit daca nu folosesti
RETURN
pentru a o opri. De exemplu, poti scrie bucla DO
anterioara cu un LOOP
simplu.(loop (when (> (get-universal-time) *some-future-date*) (return)) (format t "Waiting~%") (sleep 60))
LOOP
-ul extins e cu totul alt animal. Se distinge prin folosirea anumitor cuvinte cheie ale loop care implementeaza un limbaj special pentru a exprima idiomuri de ciclare. Merita observat ca nu toti programatorii Lisp iubesc limbajul LOOP
extins. Cel putin unul din proiectantii originali ai Common Lisp l-a urit. Detractorii se plang ca sintaxa este totalmente ne-Lisp (cu alte cuvinte, nu are suficiente paranteze). Fanii contraataca, pretinzand ca asta e si scopul: constructiile complicate de ciclare sunt suficient de greu de inteles si fara a fi incapsulate in sintaxa criptica a lui DO
. E mai bine, zic ei, sa exista o sintaxa ceva mai stufoasa care iti da niste indicii despre ce se intampla acolo. De exemplu, uite o bucla idiomatica
DO
care colecteaza numerele de la 1 la 10 intr-o lista:(do ((nums nil) (i 1 (1+ i))) ((> i 10) (nreverse nums)) (push i nums)) ==> (1 2 3 4 5 6 7 8 9 10)Un programator Lisp experimentat nu va avea probleme sa inteleaga codul respectiv -- trebuie doar sa intelegi forma de baza a buclei
DO
si sa recunosti idiomul PUSH
/NREVERSE
pentru construirea de liste. Dar nu e chiar transparent. Versiunea LOOP
, pe de alta parte, aproape arata ca o propozitie in limba engleza.(loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)Urmatoarele exemple ilustreaza tot cazuri simple de utilizare a buclei
LOOP
. Exemplul urmator insumeaza patratelor primelor zece numere intregi:(loop for x from 1 to 10 summing (expt x 2)) ==> 385Exemplul urmator numara vocalele dintr-un string:
(loop for x across "the quick brown fox jumps over the lazy dog" counting (find x "aeiou")) ==> 11Exemplul de mai jos calculeaza al unsprezecilea numar Fibonacci, similar cu bucla
DO
de mai devreme: (loop for i below 10 and a = 0 then b and b = 1 then (+ b a) finally (return a))Simbolurile
across
, and
, below
, collecting
, counting
, finally
, for
, from
, summing
, then
, and to
sunt cuvinte cheie din limbajul LOOP
si identifica buclele respective ca fiind instante ale lui LOOP
extins. 8Pastrez detaliile lui
LOOP
pentru Capitolul 22, dar merita mentionat aici ca alt exemplu al modului in care macrourile pot fi folosite pentru a extinde limbajul de baza. In timp ce LOOP
are propriul limbaj pentru exprimarea constructiilor de buclare, nu te desparte de restul Lisp-ului. Cuvintele cheie din bucle sunt parsate conform cu gramatica buclei, dar restul codului dintr-un LOOP
este cod Lisp obisnuit. Si mai merita mentionat o data faptul ca in timp ce macroul
LOOP
este sensibil mai complicat decat macrouri ca WHEN
sau UNLESS
, totusi este doar inca un macro. Daca nu era inclus in biblioteca standard, ar fi putut fi imlementat de tine, sau intr-o biblioteca terta.Acestea fiind zise inchei turul nostru prin macrourile de constructii de control de baza. Acum esti pregatit sa te uiti mai indeaproape la cum iti poti defini propriile macrouri.
1Pentru a vedea cum arata aceste neintelegeri, cauta un topic de Usenet mai lung mentionat atat in -posted comp.lang.lisp cat si oricare alt grup din comp.lang.* avand macro in subiect. O parafraza schematica vine cam asa:
Programator Lisp: "Lisp este cel mai tare datorita macrourilor!";
Programator non-Lisp: "Zici ca Lisp e bun datorita macrourilor?! Pai macrourile sunt oribile si ticaloase; Lisp trebuie ca este oribil si ticalos."
2Alta clasa importanta de constructii lingvistice definite cu macrouri sunt cele definitionale, gen
3De fapt nu poti rula definitia asta in Lisp pentru ca este ilegal sa redefinesti nume din pachetul
4Operatorii speciali, daca trebuie sa stii, sunt
5
6O varianta a lui
7
8Cuvinte cheie LOOP este o denumire oarecum gresita, deoarece nu sunt simboluri cuvinte cheie. De fapt, pe
Programator Lisp: "Lisp este cel mai tare datorita macrourilor!";
Programator non-Lisp: "Zici ca Lisp e bun datorita macrourilor?! Pai macrourile sunt oribile si ticaloase; Lisp trebuie ca este oribil si ticalos."
2Alta clasa importanta de constructii lingvistice definite cu macrouri sunt cele definitionale, gen
DEFUN
, DEFPARAMETER
, DEFVAR
si altele. In Capitolul 24 iti vei defini propriile macrouri definitionale pentru a scrie cod concis care sa citeasca si sa scrie date binare.3De fapt nu poti rula definitia asta in Lisp pentru ca este ilegal sa redefinesti nume din pachetul
COMMON-LISP
, de unde vine WHEN
. Daca chiar vrei sa incerci sa scrii un astfel de macro, ar trebui sa schimbi numele in altceva, cum ar fi my-when
.4Operatorii speciali, daca trebuie sa stii, sunt
TAGBODY
si GO
. Nu e cazul sa-i discutam acum, dar ii voi acoperi in Capitolul 20.5
DOLIST
este similar cu foreach
din Perl sau for
din Python. Java a adaugat o constructie de ciclare similara cu bucla "imbunatatita" (enhanced) for
din Java 1.5, ca parte a JSR-201. De observat ce diferenta mare aduc macrourile. Un programator Lisp care observa un model in cod poate sa scrie un macro care sa ii ofere o abstractiune pentru acea abstractiune la nivel de cod sursa. Un programator Java care observa acelasi model trebuie sa convinga pe Sun ca acea abstractiune merita adaugata in limbaj. Apoi Sun trebuie sa publice un JSR si sa convoace un "grup de experti" la nivel de industrie pentru a discuta toata chestia asta. Procesul respectiv dureaza in medie 18 luni -- conform Sun. Apoi, programatorii de compilatoare trebuie sa isi actualizeze compilatoarele pentru a suporta noile capabilitati. Si chiar si cand compilatorul favorit al unui programator Java suporta acea versiune noua, probabil inca nu are voie sa o foloseasca pana cand nu poate sa intrerupa compatibilitatea la nivel de cod sursa cu versiunile mai vechi de Java. Deci o iritare pe care programatorii Common Lisp o pot rezolva in cinci minute ii chinuie pe programatorii Java ani de zile.6O varianta a lui
DO
, DO*
, atribuie fiecarei variabile valoarea corespunzatoare inainte de a evalua forma pas pentru variabilele ulterioare. Pentru mai multe detalii, consulta referinta Common Lisp preferata.7
DOTIMES
este preferat si pentru ca expansiunea macroului probabil va include declaratii care permit compilatorului sa genereze cod mai eficient.8Cuvinte cheie LOOP este o denumire oarecum gresita, deoarece nu sunt simboluri cuvinte cheie. De fapt, pe
LOOP
nu-l intereseaza din ce pachet vin simbolurile respective. Cand macroul LOOP
isi parseaza corpul, el considera orice simboluri cu nume corespunzatoare ca fiind echivalente. Ai putea sa folosesti si cuvinte cheie adevarate daca ai vrea -- :for
, :across
si tot asa -- pentru ca si ele au numele corecte. Dar majoritatea programatorilor folosesc simboluri obisnuite. Din cauza ca cuvintele cheie ale buclei sunt folosite numai drept marcatori sintactici, nu conteaza daca sunt folosite si in alte scopuri -- cum ar fi nume de functii sau variabile.
Niciun comentariu:
Trimiteți un comentariu
S-ar putea sa nu vedeti comentariul aparand imediat, asta inseamna ca el asteapta aprobarea mea. Aceasta e o masura anti-SPAM.