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

10

Макросы

Лисп-код запи­сы­ва­ет­ся­ в виде­ списков,­ кото­рые­ сами­ явля­ют­ся­ Лиспобъек­та­ми­. В разделе 2.3 гово­ри­лось­ о том, что эта особен­ность­ предо­­ ставля­ет­ возмож­ность­ писать­ програм­мы,­ кото­рые­ пишут­ другие­ про­ граммы­. В этой главе­ пока­за­но,­ как пере­сту­пить­ черту­ между­ выра­же­­ ниями­ и кодом­.

10.1. Eval

Очевид­но,­ что созда­вать­ выра­же­ния­ нужно­ путем­ вызо­ва­ list. Одна­ко­ возни­ка­ет­ другой­ вопрос:­ как объяс­нить­ Лиспу,­ что создан­ный­ спи­ сок – это код? Для этого­ суще­ст­ву­ет­ функция­ eval, вычис­ляю­щая­ за­ данное­ выра­же­ние­ и возвра­щаю­щая­ его значе­ние:­

>(eval ’(+ 1 2 3))

6

>(eval ’(format t "Hello")) Hello

NIL

Да, именно­ об eval мы гово­ри­ли­ все это время­. Следую­щая­ функция­ реали­зу­ет­ некое­ подо­бие­ toplevel:

(defun our-toplevel () (do ()

(nil)

(format t "~%> ") (format (eval (read)))))

По этой причи­не­ toplevel так­же назы­­вают­ read-eval-print loop (цикл чтение­-вычис­ле­ние­-печать)­ .

Вызов­ eval – один из спосо­бов­ пере­шаг­нуть­ грани­цу­ между­ списка­ми­ и кодом­. Одна­ко­ это не лучший­ путь:

10.1. Eval

171

1.Он неэф­фек­ти­вен:­ eval полу­ча­ет­ простой­ список,­ и она выну­ж­де­на­ его либо­ скомпи­ли­ро­вать,­ либо­ вычис­лить­ в интер­пре­та­то­ре­. Несо­­ мненно,­ оба этих вари­ан­та­ намно­го­ медлен­­нее, чем испол­не­ние­ уже скомпи­ли­ро­ван­но­го­ кода­.

2.Выра­же­ние­ вычис­ля­ет­ся­ вне како­го­-либо­ лекси­че­ско­го­ контек­ста­. Если,­ напри­мер,­ вызвать­ eval внутри­ let, выра­же­ния,­ пере­дан­ные­ в eval, не смогут­ ссылать­ся­ на пере­мен­ные,­ опре­де­лен­­ные в let.

Суще­ст­ву­ют­ гораз­до­ более­ опти­маль­ные­ спосо­бы­ гене­ра­ции­ кода­ (см. описа­ние­ в следую­щем­ разде­ле)­ . А что каса­ет­ся­ eval, то ее мож­но ис­ пользо­вать,­ пожа­луй,­ лишь для созда­ния­ чего­-то вроде­ циклов­ toplevel.

Поэто­му­ для програм­ми­стов­ основ­ная­ ценность­ eval заклю­ча­ет­ся­ в том, что эта функция­ выра­жа­ет­ концеп­ту­аль­ную­ суть Лиспа­. Мож­но счи­ тать, что eval опре­де­ле­на­ как большое­ выра­же­ние­ cond:

(defun eval (expr env) (cond ...

((eql (car expr) ’quote) (cadr expr))

...

(t (apply (symbol-function (car expr)) (mapcar #’(lambda (x)

(eval x env)) (cdr expr))))))

Большин­ст­во­ выра­же­ний­ обра­ба­ты­ва­ют­ся­ послед­ним­ усло­ви­ем,­ кото­­ рое гово­рит:­ взять функцию,­ имя кото­рой­ запи­са­но­ в car, вычис­лить­ ар­ гумен­ты­ из cdr и вернуть­ резуль­тат­ при­мене­ния­ функции­ к хвосту­ (cdr) списка­1.

Тем не менее­ для выра­же­ния­ вида­ (quote x) такой­ подход­ непри­ме­ним,­ посколь­ку­ вся суть quote в том, чтобы­ защи­тить­ свой аргу­мент­ от вы­ числе­ния­. Это значит,­ что нам необ­хо­ди­мо­ специ­аль­ное­ усло­вие­ для quote. Так мы при­ходим­ к сути­ поня­тия­ «специ­аль­ный­ опера­тор» ­– это опера­тор,­ для кото­ро­го­ в eval имеет­ся­ особое­ усло­вие­.

Функции­ coerce и compile так­же предос­тав­ля­ют­ возмож­ность­ перей­ти­ от списков­ к коду­. Лямбда­-выра­же­ние­ можно­ превра­тить­ в функцию:­

>(coerce ’(lambda (x) x) ’function) #<Interpreted-Function BF9D96>

Спомо­щью­ функции­ compile с первым­ аргу­мен­том­ nil так­же мож­но скомпи­ли­ро­вать­ лямбда­-выра­же­ние,­ пере­дан­ное­ вторым­ аргу­мен­том:­

>(compile nil ’(lambda (x) (+ x 2)))

#<Compiled-Function DF55BE>

1Как и реаль­ная­ eval, наша­ функция­ имеет­ еще один аргу­мент­ (env), пред­ ставляю­щий­ лекси­че­ское­ окру­же­ние­. Неточ­ность­ нашей­ моде­ли­ eval состо­­ ит в том, что она полу­ча­ет­ функцию­ перед­ тем, как присту­па­ет­ к обра­бот­ке­ аргу­мен­тов,­ в то время­ как в Common Lisp поря­док­ выпол­не­ния­ этих опера­­ ций специ­аль­ным­ обра­зом­ не опре­де­лен­.

172

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

NIL

NIL

Посколь­ку­ coerce и compile могут­ прини­мать­ списки­ в каче­ст­ве­ аргу­мен­­ тов, програм­ма­ может­ созда­вать­ новые­ функции­ на лету­. Одна­ко­ этот метод­ так­же нельзя­ назвать­ лучшим,­ так как он имеет­ те же недос­тат­­ ки, что и eval.

Причи­на­ неэф­фек­тив­но­сти­ eval, coerce и compile в том, что они пере­се­­ кают­ грани­цу­ между­ списка­ми­ и кодом­ и созда­ют­ функции­ в момент­ выпол­не­ния­ (run-time). Пере­се­че­ние­ грани­цы ­– доро­гое­ удоволь­ст­вие­. В большин­ст­ве­ случа­ев­ это лучше­ делать­ во время­ компи­ля­ции,­ а не в процес­се­ выпол­не­ния­ програм­мы­. В следую­щем­ разде­ле­ будет­ пока­за­­ но, как это сделать­.

10.2. Макросы

Наибо­лее­ распро­стра­нен­ный­ способ­ писать­ програм­мы,­ кото­рые­ будут­ писать­ програм­мы, ­– это созда­ние­ макро­сов­. Макро­сы ­– это опера­то­ры,­ кото­рые­ реали­зу­ют­ся­ через­ трансфор­ма­цию­. Опре­де­ляя­ макрос,­ вы ука­ зывае­те,­ как его вызов­ должен­ быть пре­обра­зо­ван­. Само­ преоб­ра­зо­ва­ние­ авто­ма­ти­че­ски­ выпол­ня­ет­ся­ компи­ля­то­ром­ и назы­ва­ет­ся­ раскры­ти­ем­ макро­са­ (macro-expansion). Код, полу­чен­ный­ в резуль­та­те­ раскры­тия­ макро­са,­ есте­ст­вен­ным­ обра­зом­ стано­вит­ся­ частью­ програм­мы,­ как ес­ ли бы он был набран­ вами­.

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

(defmacro nil! (x) (list ’setf x nil))

Данный­ макрос­ созда­ет­ новый­ опера­тор­ nil!, кото­рый­ при­нима­ет­ один аргу­мент­. Вызов­ вида­ (nil! a) будет­ преоб­ра­зо­ван­ в (setf a nil) и лишь за­ тем скомпи­ли­ро­ван­ или вычис­лен­. Таким­ обра­зом,­ если­ набрать­ (nil! x)

вtoplevel,

>(nil! x) NIL

>x

NIL

это в точно­сти­ экви­ва­лент­но­ набо­ру­ выра­же­ния­ (setf x nil).

Чтобы­ протес­ти­ро­вать­ функцию,­ ее вызы­­вают­. Чтобы­ протес­ти­ро­вать­ макрос,­ его раскры­ва­ют­. Функция­ macroexpand–1 прини­ма­ет­ вызов­ мак­ роса­ и возвра­ща­ет­ резуль­тат­ его раскры­тия:­

10.3. Обратная кавычка

173

> (macroexpand–1 ’(nil! x)) (SETF X NIL)

T

Макрос­ может­ раскры­вать­ся­ в другой­ макро­вы­зов­. Когда­ компи­ля­тор­ (или toplevel) встреча­ет­ такой­ макро­вы­зов,­ он раскры­ва­ет­ этот макрос­ до тех пор, пока­ в коде­ не оста­нет­ся­ ни одно­го­ друго­го­ макро­вы­зо­ва­.

Секрет­ пони­ма­ния­ макро­сов­ в осозна­нии­ их реали­за­ции­. Они – не бо­ лее чем функции,­ преоб­ра­зую­щие­ выра­же­ния­. К приме­ру,­ если­ вы пе­ реда­ди­те­ выра­же­ние­ вида­ (nil! a) в следую­щую­ функцию:­

(lambda (expr)

(apply #’(lambda (x) (list ’setf x nil)) (cdr expr)))

то она вернет­ (setf a nil). Приме­няя­ defmacro, вы выпол­няе­те­ похо­жую­ рабо­ту­. Все, что дела­ет­ macroexpand–1, когда­ встреча­ет­ выра­же­ние,­ car кото­ро­го­ соот­вет­ст­ву­ет­ имени­ одно­го­ из макро­сов, ­– это пере­на­прав­ля­­ ет это выра­же­ние­ в соот­вет­ст­вую­щую­ функцию­.

10.3. Обратная кавычка

Макрос­ чтения­ обрат­ная­ кавыч­ка­ (backquote) позво­ля­ет­ строить­ спи­ ски на осно­ве­ специ­аль­ных­ шабло­нов­. Она широ­ко­ исполь­зу­ет­ся­ при опре­де­ле­нии­ макро­сов­. Обычной­ кавыч­ке­ на клавиа­ту­ре­ соот­вет­ст­ву­ет­ закры­ваю­щая­ прямая­ кавыч­ка­ (апост­роф),­ а обрат­ной ­– откры­ваю­щая­. Ее назы­­вают­ обрат­ной­ из-за ее накло­на­ влево­.

Обрат­ная­ кавыч­ка,­ исполь­зо­ван­ная­ сама­ по себе,­ экви­ва­лент­на­ обыч­ ной кавыч­ке:­

> ‘(a b c) (A B C)

Как и обычная­ кавыч­ка,­ обрат­ная­ кавыч­ка­ предот­вра­ща­ет­ вычис­ле­­ ние выра­же­ния­.

Преиму­ще­ст­вом­ обрат­ной­ кавыч­ки­ явля­ет­ся­ возмож­ность­ выбо­роч­но­го­ вычис­ле­ния­ частей­ выра­же­ния­ с помо­щью­ , (запя­той)­ и ,@ (запя­тая­-эт). Любое­ подвы­ра­же­ние,­ предва­ряе­мое­ запя­той,­ будет­ вычис­ле­но­. Соче­­ тая обрат­ную­ кавыч­ку­ и запя­тую,­ можно­ строить­ шабло­ны­ списков:­

>(setf a 1 b 2)

2

>‘(a is ,a and b is ,b) (A IS 1 AND B IS 2)

Исполь­зуя­ обрат­ную­ кавыч­ку­ вместо­ вызо­ва­ list, мы можем­ запи­сы­­ вать опре­де­ле­ния­ макро­сов,­ кото­рые­ будут­ выгля­деть­ так же, как ре­ зультат­ их раскры­тия­. К приме­ру,­ опера­тор­ nil! может­ быть опре­де­лен­ следую­щим­ обра­зом:­

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