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

174

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

(defmacro nil! (x) ‘(setf ,x nil))

Запя­тая­-эт дейст­ву­ет­ похо­жим­ обра­зом,­ но вставля­ет­ поэле­мент­но­ свой аргу­мент­ (кото­рый­ должен­ быть списком)­ в сере­ди­ну­ друго­го­ списка:­

>(setf lst ’(a b c)) (A B C)

>‘(lst is ,lst) (LST IS (A B C))

>‘(its elements are ,@lst) (ITS ELEMENTS ARE A B C)

Это может­ оказать­ся­ полез­ным­ для макро­сов,­ кото­рые­ исполь­зу­ют­ ос­ таточ­ный­ аргу­мент,­ напри­мер,­ для представ­ле­ния­ тела­ кода­. Предпо­ло­­ жим, мы хотим­ создать­ макрос­ while, кото­рый­ вычис­ля­ет­ свои аргу­мен­­ ты до тех пор, пока­ прове­роч­ное­ выра­же­ние­ оста­ет­ся­ истин­ным:­

> (let ((x 0)) (while (< x 10)

(princ x) (incf x)))

0123456789 NIL

Мы можем­ опре­де­лить­ такой­ макрос,­ исполь­зуя­ оста­точ­ный­ пара­метр,­ кото­рый­ собе­рет­ все выра­же­ния­ тела­ макро­са­ в список,­ а затем­ этот список­ будет­ встроен­ в резуль­тат­ раскры­тия­ макро­са­ благо­да­ря­ ,@:

(defmacro while (test &rest body) ‘(do ()

((not ,test)) ,@body))

10.4. Пример: быстрая сортировка

На рис. 10.1 приве­ден­ пример­1 функции,­ полно­стью­ постро­ен­ной­ на мак­ росах, ­– функции,­ выпол­няю­щей­ сорти­ров­ку­ векто­ров­ с помо­щью­ алго­­ ритма­ быст­рой­ сорти­ров­ки­ (quicksort).°

Алго­ритм­ быст­рой­ сорти­ров­ки­ выпол­ня­ет­ся­ следую­щим­ обра­зом:­

1.Один из элемен­тов­ прини­ма­ет­ся­ в каче­ст­ве­ опорно­го­. Многие­ реали­­ зации­ выби­ра­ют­ элемент­ из сере­ди­ны­ векто­ра­.

2.Затем­ произ­во­дит­ся­ разбие­ние­ векто­ра,­ при этом его элемен­ты­ пере­­ ставля­ют­ся­ места­ми­ до тех пор, пока­ все элемен­ты,­ меньшие­ опорно­­ го, не будут­ нахо­дить­ся­ по левую­ сторо­ну­ от больших­ или равных­ ему.

3.Нако­нец,­ если­ хотя­ бы одна­ из частей­ состо­ит­ из двух и более­ элемен­­ тов, алго­ритм­ приме­ня­ет­ся­ рекур­сив­но­ к каждо­му­ из сегмен­тов­.

1Код на рис. 10.1 содер­жит­ слегка­ исправ­лен­ный­ код. Ошибка­ найде­на­ Дже­ дом Кросби­. – Прим. перев­.

10.5. Проектирование макросов

175

 

 

 

 

(defun quicksort (vec l r)

 

 

(let ((i l)

 

 

(j r)

 

 

(p (svref vec (round (+ l r) 2))))

; 1

 

(while (<= i j)

; 2

 

(while (< (svref vec i) p) (incf i))

 

 

(while (> (svref vec j) p) (decf j))

 

 

(when (<= i j)

 

 

(rotatef (svref vec i) (svref vec j))

 

 

(incf i)

 

 

(decf j)))

 

 

(if (>= (- j l) 1) (quicksort vec l j))

; 3

 

(if (>= (- r i) 1) (quicksort vec i r)))

 

 

vec)

 

 

 

 

Рис. 10.1. Быст­рая­ сорти­ров­ка­

С каждым­ после­дую­щим­ вызо­вом­ сорти­руе­мый­ список­ стано­вит­ся­ ко­ роче,­ и так до тех пор, пока­ весь список­ не будет­ отсор­ти­ро­ван­.

Реали­за­ция­ этого­ алго­рит­ма­ (рис. 10.1) требу­ет­ указа­ния­ векто­ра­ и двух чисел,­ задаю­щих­ диапа­зон­ сорти­ров­ки­. Элемент,­ нахо­дя­щий­ся­ в сере­­ дине­ этого­ диапа­зо­на,­ выби­ра­ет­ся­ в каче­ст­ве­ опорно­го­ элемен­та­ (p). За­ тем по мере­ продви­же­ния­ вдоль векто­ра­ к оконча­нию­ диапа­зо­на­ произ­­ водят­ся­ пере­ста­нов­ки­ его элемен­тов,­ кото­рые­ либо­ слишком­ большие,­ чтобы­ нахо­дить­ся­ слева,­ либо­ слишком­ малые,­ чтобы­ нахо­дить­ся­ спра­ ва. (Пере­ста­нов­ка­ двух элемен­тов­ осуще­ст­в­ля­ет­ся­ с помо­щью­ rotatef.) Далее­ эта проце­ду­ра­ повто­ря­ет­ся­ рекур­сив­но­ для каждой­ полу­чен­­ной части,­ содер­жа­щей­ более­ одно­го­ элемен­та­.

Поми­мо­ макро­са­ while, опре­де­лен­­ного­ в преды­ду­щем­ разде­ле,­ в quicksort (рис. 10.1) задей­ст­во­ва­ны­ встроен­ные­ макро­сы­ when, incf, decf и rotatef. Их исполь­зо­ва­ние­ суще­ст­вен­­но упро­ща­ет­ код, делая­ его акку­рат­ным­

ипонят­ным­.

10.5.Проектирование макросов

Напи­са­ние­ макро­сов ­– это отдель­ная­ разно­вид­ность­ програм­ми­ро­ва­­ ния, имеющая­ собст­вен­­ные уникаль­ные­ цели­ и пробле­мы­. Умение­ из­ менять­ пове­де­ние­ компи­ля­то­ра­ отчас­ти­ похо­же­ на возмож­ность­ изме­­ нять сам компи­ля­тор­. Поэто­му­ при разра­бот­ке­ макро­сов­ нужно­ мыс­ лить как созда­тель­ язы­ка.

В этом разде­ле­ будут­ рассмот­ре­ны­ основ­ные­ трудно­сти,­ связан­ные­ с на­ писа­ни­ем­ макро­сов,­ и мето­ди­ки­ их разре­ше­ния­. В каче­ст­ве­ при­мера­ мы созда­дим­ макрос­ ntimes, вычис­ляю­щий­ свое тело­ n раз:

> (ntimes 10 (princ "."))

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

..........

NIL

Ниже­ при­веде­но­ некор­рект­ное­ опре­де­ле­ние­ макро­са­ ntimes, иллю­ст­ри­­ рующее­ неко­то­рые­ пробле­мы,­ связан­ные­ с проек­ти­ро­ва­ни­ем­ макро­сов:­

(defmacro ntimes (n &rest body)

; wrong

‘(do ((x 0 (+ x 1)))

 

((>= x ,n))

 

,@body))

 

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

Первой­ пробле­мой,­ о кото­рой­ долж­ны думать­ разра­бот­чи­ки­ макро­сов,­ явля­ет­ся­ непред­на­ме­рен­ный­ захват­ пере­мен­ных­. Это случа­ет­ся,­ когда­ имя пере­мен­ной,­ исполь­зуе­мой­ в раскры­тии­ макро­са,­ совпа­да­ет­ с име­ нем пере­мен­ной,­ кото­рая­ уже суще­ст­ву­ет­ в том окру­же­нии,­ куда­ встав­ ляет­ся­ раскры­тие­ макро­са­. Некор­рект­ное­ опре­де­ле­ние­ ntimes созда­ет­ пере­мен­ную­ с именем­ x. Поэто­му­ если­ макрос­ вызы­­вает­ся­ там, где уже суще­ст­ву­ет­ пере­мен­ная­ с таким­ же именем,­ резуль­тат­ его вы­полне­ния­ может­ не соот­вет­ст­во­вать­ нашим­ ожида­ни­ям:­

> (let ((x 10)) (ntimes 5

(setf x (+ x 1)))

x)

10

Мы предпо­ла­га­ли,­ что значе­ние­ x будет­ увели­че­но­ в пять раз и в итоге­ будет­ равно­ 15. Но посколь­ку­ пере­мен­ная­ x исполь­зу­ет­ся­ еще и внутри­ макро­са­ как итери­руе­мая­ пере­мен­ная­ в do, setf будет­ увели­чи­вать­ зна­ чение­ этой пере­мен­ной,­ а не той, что предпо­ла­га­лось­. Раскрыв­ этот макрос,­ мы увидим,­ что преды­ду­щее­ выра­же­ние­ выгля­дит­ следую­щим­ обра­зом:­

(let ((x 10))

(do ((x 0 (+ x 1))) ((> x 5))

(setf x (+ x 1)))

x)

Наибо­лее­ общим­ реше­ни­ем­ будет­ не исполь­зо­вать­ обычные­ симво­лы­ в тех местах,­ где они могут­ быть захва­че­ны­. Вместо­ этого­ можно­ исполь­­ зовать­ gensym (см. раздел 8.4). В связи­ с тем что read интер­ни­ру­ет­ каж­ дый встречен­ный­ символ,­ gensym никоим­ обра­зом­ не будет­ равен­ (с точ­ ки зрения­ eql) любо­му­ друго­му­ симво­лу,­ встречаю­ще­му­ся­ в тексте­ про­ граммы­. Пере­пи­сав­ наше­ опре­де­ле­ние­ ntimes с исполь­зо­ва­ни­ем­ gensym вместо­ x, мы сможем­ изба­вить­ся­ от выше­опи­сан­ной­ пробле­мы:­

(defmacro ntimes (n &rest body)

; wrong

(let ((g (gensym)))

 

10.5. Проектирование макросов

177

‘(do ((,g 0 (+ ,g 1))) ((>= ,g ,n))

,@body)))

Тем не менее­ в данном­ опре­де­ле­нии­ по-прежне­му­ кроет­ся­ другая­ про­ блема:­ повтор­ное­ вычис­ле­ние­. Посколь­ку­ первый­ аргу­мент­ встраи­вает­­ ся непо­сред­ст­вен­­но в вызов­ do, он будет­ вычис­лять­ся­ на каждой­ итера­­ ции. Эта ошибка­ ясно­ демон­ст­ри­ру­ет­ся­ приме­ром,­ в кото­ром­ первое­ вы­ раже­ние­ содер­жит­ побоч­ные­ эффек­ты:­

> (let ((v 10))

(ntimes (setf v (- v 1)) (princ ".")))

.....

NIL

Посколь­ку­ началь­ное­ значе­ние­ v равно­ 10 и setf возвра­ща­ет­ значе­ние­ своего­ второ­го­ аргу­мен­та,­ мы ожида­ли­ полу­чить­ девять­ точек,­ одна­ко­ в дейст­ви­тель­но­сти­ их только­ пять.

Чтобы­ разо­брать­ся­ с причи­ной­ тако­го­ эффек­та,­ посмот­рим­ на резуль­тат­ раскры­тия­ макро­са:­

(let ((v 10))

(do ((#:g1 0 (+ #:g1 1)))

((>= #:g1 (setf v (- v 1)))) (princ ".")))

На каждой­ итера­ции­ мы сравни­ва­ем­ значе­ние­ пере­мен­ной­ (gensym при печа­ти­ обычно­ предва­ря­ет­ся­ #:) не с числом­ 9, а с выра­же­ни­ем,­ кото­рое­ уменьша­ет­ся­ на едини­цу­ при каждом­ вызо­ве­.

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

(defmacro ntimes (n &rest body) (let ((g (gensym))

(h (gensym))) ‘(let ((,h ,n))

(do ((,g 0 (+ ,g 1))) ((>= ,g ,h))

,@body))))

Эта версия­ ntimes явля­ет­ся­ полно­стью­ коррект­ной­.

Непред­на­ме­рен­ные­ повтор­ные­ вычис­ле­ния­ и захват­ пере­мен­ных ­– ос­ новные­ пробле­мы,­ возни­каю­щие­ при разра­бот­ке­ макро­сов,­ но есть и другие­. С опытом­ видеть­ ошибки­ в макро­сах­ станет­ так же легко­ и ес­ тест­вен­­но, как преду­пре­ж­дать­ деле­ние­ на ноль. Но посколь­ку­ макро­сы­ наде­ля­ют­ нас новы­ми­ возмож­но­стя­ми,­ то и сложно­сти,­ с кото­ры­­ми нам придет­ся­ столкнуть­ся,­ тоже­ новы­.

Ваша­ реали­за­ция­ Common Lisp содер­жит­ множе­ст­во­ приме­ров,­ на кото­­ рых вы може­те­ поучить­ся­ проек­ти­ро­вать­ макро­сы­. Раскры­вая­ вызо­вы­

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