Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
28
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

178

Глава 10. Макросы

встроен­ных­ макро­сов,­ вы сможе­те­ понять­ их устрой­ст­во­. Ниже­ при­ве­ ден резуль­тат­ раскры­тия­ cond для большин­ст­ва­ реали­за­ций:­

> (pprint (macroexpand-1 ’(cond (a b) (c d e)

(t f))))

(IF A B

(IF C

(PROGN D E) F))

Функция­ pprint, печа­таю­щая­ выра­же­ния­ с необ­хо­ди­мы­ми­ отсту­па­ми,­ сдела­ет­ выво­ди­мый­ код легко­чи­тае­мым­.

10.6. Обобщенные ссылки

Посколь­ку­ макро­вы­зов­ раскры­ва­ет­ся­ непо­сред­ст­вен­­но в том месте­ ко­ да, где он был вызван,­ любой­ макро­вы­зов,­ раскры­ваю­щий­ся­ в выра­же­­ ние, кото­рое­ может­ быть первым­ аргу­мен­том­ setf, сам может­ быть пер­ вым аргу­мен­том­ setf. Опре­де­лим,­ напри­мер,­ сино­ним­ car:

(defmacro cah (lst) ‘(car ,lst))

Так как выра­же­ние­ с car может­ быть первым­ аргу­мен­том­ setf, то и ана­ логич­ный­ вызов­ с cah также­ может­ иметь место:­

> (let ((x (list ’a ’b ’c))) (setf (cah x) 44)

x) (44 B C)

Напи­са­ние­ макро­са,­ кото­рый­ само­стоя­тель­но­ раскры­ва­ет­ся­ в вызов­ setf, – менее­ триви­аль­ная­ зада­ча,­ несмот­ря­ на кажу­щую­ся­ просто­ту­. На первый­ взгляд мож­но опре­де­лить­ incf, напри­мер­ так:

(defmacro incf

(x

&optional (y 1))

; wrong

‘(setf ,x (+

,x

,y)))

 

Но такое­ опре­де­ле­ние­ не рабо­та­ет­. Следую­щие­ два выра­же­ния­ не экви­­ валент­ны:­

(setf (car (push 1 lst)) (1+ (car (push 1 lst))))

(incf (car (push 1 lst)))

Если­ lst изна­чаль­но­ равно­ nil, то второе­ выра­же­ние­ уста­но­вит­ его в (2), тогда­ как первое­ выра­же­ние­ уста­но­вит­ его в (1 2).

Common Lisp предос­тав­ля­ет­ define-modify-macro как спо­соб напи­са­ния­ огра­ни­чен­но­го­ класса­ макро­сов­ поверх­ setf. Он прини­ма­ет­ три аргу­мен­­ та: имя макро­са,­ допол­ни­тель­ные­ аргу­мен­ты­ (место,­ подле­жа­щее­ изме­­ нению,­ явля­ет­ся­ неяв­ным­ первым­ аргу­мен­том)­ и имя функции,­ кото­рая­

10.7. Пример: макросы-утилиты

179

вернет­ новое­ значе­ние­ задан­но­го­ места­. Теперь­ мы можем­ опре­де­лить­ incf так:

(define-modify-macro our-incf (&optional (y 1)) +)

А вот версия­ push для добав­ле­ния­ элемен­та­ в конец­ списка:­

(define-modify-macro append1f (val)

(lambda (lst val) (append lst (list val))))

Послед­ний­ макрос­ рабо­та­ет­ следую­щим­ обра­зом:­

> (let ((lst ’(a b c))) (append1f lst ’d) lst)

(A B C D)

Между­ прочим,­ макро­сы­ push и pop не могут­ быть опре­де­ле­ны­ через­ de­ fine­-modify-macro, так как в push изме­няе­мое­ место­ не явля­ет­ся­ первым­ аргу­мен­том,­ а в случае­ pop его возвра­щае­мое­ значе­ние­ не явля­ет­ся­ из­ менен­ным­ объек­том­.

10.7.Пример: макросы-утилиты

Вразделе 6.4 рассмат­ри­ва­лась­ концеп­ция­ утилит,­ опера­то­ров­ обще­го­ назна­че­ния­. С помо­щью­ макро­сов­ мы можем­ созда­вать­ утили­ты,­ кото­­ рые не могут­ быть созда­ны­ как функции­. Несколь­ко­ подоб­ных­ приме­­ ров мы уже виде­ли:­ nil!, ntimes, while. Все они позво­ля­ют­ управлять­ процес­сом­ вычис­ле­ния­ аргу­мен­тов,­ а поэто­му­ могут­ быть реали­зо­ва­ны­ только­ в виде­ макро­сов­. В этом разде­ле­ вы найде­те­ больше­ приме­ров­ утилит­-макро­сов­. На рис. 10.2 при­веде­на­ выбор­ка­ макро­сов,­ кото­рые­ на практи­ке­ дока­за­ли­ свое право­ на суще­ст­во­ва­ние­.

Первый­ из них, for, по устрой­ст­ву­ напо­ми­на­ет­ while (стр. 174). Его тело­ вычисля­ет­ся­ в цикле,­ каждый­ раз для ново­го­ значе­ния­ пере­мен­ной:­

> (for x 1 8 (princ x))

12345678 NIL

Вы­глядит­ несо­мнен­но­ проще,­ чем анало­гич­ное­ выра­же­ние­ с do:

(do ((x 1 (1+ x))) ((> x 8))

(princ x))

Резуль­тат­ раскры­тия­ выра­же­ния­ с for будет­ очень похож­ на выра­же­ние­ с do:

(do ((x 1 (1+ x)) (#:g1 8)) ((> x #:g1))

(princ x))

180

Глава 10. Макросы

(defmacro for (var start stop &body body) (let ((gstop (gensym)))

‘(do ((,var ,start (1+ ,var)) (,gstop ,stop))

((> ,var ,gstop)) ,@body)))

(defmacro in (obj &rest choices) (let ((insym (gensym)))

‘(let ((,insym ,obj))

(or ,@(mapcar #’(lambda (c) ‘(eql ,insym ,c)) choices)))))

(defmacro random-choice (&rest exprs) ‘(case (random ,(length exprs))

,@(let ((key -1))

(mapcar #’(lambda (expr)

‘(,(incf key) ,expr)) exprs))))

(defmacro avg (&rest args)

‘(/ (+ ,@args) ,(length args)))

(defmacro with-gensyms (syms &body body) ‘(let ,(mapcar #’(lambda (s)

‘(,s (gensym))) syms)

,@body))

(defmacro aif (test then &optional else) ‘(let ((it ,test))

(if it ,then ,else)))

Рис. 10.2. Утили­ты­ на макро­сах­

Макрос­ исполь­зу­ет­ допол­ни­тель­ную­ пере­мен­ную­ для хране­ния­ верх­ ней грани­цы­ цикла­. В против­ном­ случае­ выра­же­ние­ 8 вычис­ля­лось­ бы каждый­ раз, а для более­ сложных­ случа­ев­ это неже­ла­тель­но­. Допол­ни­­ тельная­ пере­мен­ная­ созда­ет­ся­ с помо­щью­ gensym, что позво­ля­ет­ предот­­ вратить­ непред­на­ме­рен­ный­ захват­ пере­мен­ной­.

Второй­ макрос­ на рис. 10.2, in, возвра­ща­ет­ исти­ну,­ если­ его первый­ ар­ гумент­ равен­ (с точки­ зрения­ eql) хотя­ бы одно­му­ из осталь­ных­ своих­ аргу­мен­тов­. Без этого­ макро­са­ вместо­ выра­же­ния:­

(in (car expr) ’+ ’- ’*)

нам пришлось­ бы писать:­

(let ((op (car expr))) (or (eql op ’+)

(eql op ’-) (eql op ’*)))

10.7. Пример: макросы-утилиты

181

Разу­ме­ет­ся,­ первое­ выра­же­ние­ раскры­ва­ет­ся­ в подоб­ное­ этому,­ за ис­ ключе­ни­ем­ того,­ что вместо­ пере­мен­ной­ op исполь­зу­ет­ся­ gensym.

Следую­щий­ пример,­ random-choice, случай­ным­ обра­зом­ выби­ра­ет­ один из своих­ аргу­мен­тов­ и вычис­ля­ет­ его. С зада­чей­ случай­но­го­ выбо­ра­ мы уже сталки­ва­лись­ (стр. 89). Макрос­ random-choice реали­зу­ет­ его обоб­ щенный меха­низм­. Вызов­ типа:­

(random-choice (turn-left) (turn-right))

раскры­ва­ет­ся­ в:

(case (random 2) (0 (turn-left)) (1 (turn-right)))

Следую­щий­ макрос,­ with-gensyms, предна­зна­чен­ в первую­ очередь­ для исполь­зо­ва­ния­ внутри­ других­ макро­сов­. Нет ниче­го­ необыч­но­го­ в его приме­не­нии­ в макро­сах,­ кото­рые­ вызы­­вают­ gensym для несколь­ких­ пе­ ремен­ных­. С его помо­щью­ вместо:­

(let ((x (gensym)) (y (gensym)) (z (gensym)))

...)

мы можем­ напи­сать:­

(with-gensyms (x y z)

...)

Пока­ что ни один из при­веден­ных­ выше­ макро­сов­ не мог быть напи­сан­ в виде­ функции­. Из этого­ следу­ет­ прави­ло:­ макрос­ должен­ созда­вать­ся­ лишь тогда,­ когда­ вы не може­те­ напи­сать­ анало­гич­ную­ функцию­. Од­ нако­ из этого­ прави­ла­ быва­ют­ исклю­че­ния­. Напри­мер,­ иногда­ вы може­­ те опре­де­лить­ опера­тор­ как макрос,­ чтобы­ иметь возмож­ность­ выпол­­ нять какую­-либо­ рабо­ту­ на этапе­ компи­ля­ции­. Приме­ром­ может­ слу­ жить макрос­ avg, вычис­ляю­щий­ среднее­ значе­ние­ своих­ аргу­мен­тов:­

> (avg 2 4 8) 14/3

Анало­гич­ная­ функция­ будет­ иметь вид:

(defun avg (&rest args)

(/ (apply #’+ args) (length args)))

но эта функция­ выну­ж­де­на­ считать­ коли­че­ст­во­ аргу­мен­тов­ в процес­се­ испол­не­ния­. Если­ нам вдруг захо­чет­ся­ избе­жать­ этого,­ поче­му­ бы не вычис­лять­ длину­ списка­ аргу­мен­тов­ (вызо­вом­ length) на этапе­ компи­­ ляции?­

Послед­ний­ макрос­ на рис. 10.2, aif, приво­дит­ся­ в каче­ст­ве­ приме­ра­ предна­ме­рен­но­го­ захва­та­ пере­мен­ной­. Он позво­ля­ет­ ссылать­ся­ через­ пе­ ремен­ную­ it на значе­ние,­ возвра­щае­мое­ тесто­вым­ аргу­мен­том­ if. Благо­­ даря­ этому­ вместо:­

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]