- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 1. Введение
- •1.1. Новые инструменты
- •1.2. Новые приемы
- •1.3. Новый подход
- •Глава 2. Добро пожаловать в Лисп
- •2.1. Форма
- •2.2. Вычисление
- •2.3. Данные
- •2.4. Операции со списками
- •2.5. Истинность
- •2.6. Функции
- •2.7. Рекурсия
- •2.8. Чтение Лиспа
- •2.9. Ввод и вывод
- •2.10. Переменные
- •2.11. Присваивание
- •2.12. Функциональное программирование
- •2.13. Итерация
- •2.14. Функции как объекты
- •2.15. Типы
- •2.16. Заглядывая вперед
- •Итоги главы
- •Упражнения
- •Глава 3. Списки
- •3.1. Ячейки
- •3.2. Равенство
- •3.3. Почему в Лиспе нет указателей
- •3.4. Построение списков
- •3.5. Пример: сжатие
- •3.6. Доступ
- •3.7. Отображающие функции
- •3.8. Деревья
- •3.9. Чтобы понять рекурсию, нужно понять рекурсию
- •3.10. Множества
- •3.11. Последовательности
- •3.12. Стопка
- •3.13. Точечные пары
- •3.14. Ассоциативные списки
- •3.15. Пример: поиск кратчайшего пути
- •3.16. Мусор
- •Итоги главы
- •Упражнения
- •Глава 4. Специализированные структуры данных
- •4.1. Массивы
- •4.2. Пример: бинарный поиск
- •4.3. Строки и знаки
- •4.4. Последовательности
- •4.5. Пример: разбор дат
- •4.6. Структуры
- •4.7. Пример: двоичные деревья поиска
- •4.8. Хеш-таблицы
- •Итоги главы
- •Упражнения
- •Глава 5. Управление
- •5.1. Блоки
- •5.2. Контекст
- •5.3. Условные выражения
- •5.4. Итерации
- •5.5. Множественные значения
- •5.6. Прерывание выполнения
- •5.7. Пример: арифметика над датами
- •Итоги главы
- •Упражнения
- •Глава 6. Функции
- •6.1. Глобальные функции
- •6.2. Локальные функции
- •6.3. Списки параметров
- •6.4. Пример: утилиты
- •6.5. Замыкания
- •6.6. Пример: строители функций
- •6.7. Динамический диапазон
- •6.8. Компиляция
- •6.9. Использование рекурсии
- •Итоги главы
- •Упражнения
- •Глава 7. Ввод и вывод
- •7.1. Потоки
- •7.2. Ввод
- •7.3. Вывод
- •7.4. Пример: замена строк
- •7.5. Макрознаки
- •Итоги главы
- •Упражнения
- •Глава 8. Символы
- •8.1. Имена символов
- •8.2. Списки свойств
- •8.3. А символы-то не маленькие
- •8.4. Создание символов
- •8.5. Использование нескольких пакетов
- •8.6. Ключевые слова
- •8.7. Символы и переменные
- •8.8. Пример: генерация случайного текста
- •Итоги главы
- •Упражнения
- •Глава 9. Числа
- •9.1. Типы
- •9.2. Преобразование и извлечение
- •9.3. Сравнение
- •9.4. Арифметика
- •9.5. Возведение в степень
- •9.6. Тригонометрические функции
- •9.7. Представление
- •9.8. Пример: трассировка лучей
- •Итоги главы
- •Упражнения
- •Глава 10. Макросы
- •10.1. Eval
- •10.2. Макросы
- •10.3. Обратная кавычка
- •10.4. Пример: быстрая сортировка
- •10.5. Проектирование макросов
- •10.6. Обобщенные ссылки
- •10.7. Пример: макросы-утилиты
- •10.8. На Лиспе
- •Итоги главы
- •Упражнения
- •Глава 11. CLOS
- •11.1. Объектно-ориентированное программирование
- •11.2. Классы и экземпляры
- •11.3. Свойства слотов
- •11.4. Суперклассы
- •11.5. Предшествование
- •11.6. Обобщенные функции
- •11.7. Вспомогательные методы
- •11.8. Комбинация методов
- •11.9. Инкапсуляция
- •11.10. Две модели
- •Итоги главы
- •Упражнения
- •Глава 12. Структура
- •12.1. Разделяемая структура
- •12.2. Модификация
- •12.3. Пример: очереди
- •12.4. Деструктивные функции
- •12.5. Пример: двоичные деревья поиска
- •12.6. Пример: двусвязные списки
- •12.7. Циклическая структура
- •12.8. Неизменяемая структура
- •Итоги главы
- •Упражнения
- •Глава 13. Скорость
- •13.1. Правило бутылочного горлышка
- •13.2. Компиляция
- •13.3. Декларации типов
- •13.4. Обходимся без мусора
- •13.5. Пример: заранее выделенные наборы
- •13.6. Быстрые операторы
- •13.7. Две фазы разработки
- •Итоги главы
- •Упражнения
- •Глава 14. Более сложные вопросы
- •14.1. Спецификаторы типов
- •14.2. Бинарные потоки
- •14.3. Макросы чтения
- •14.4. Пакеты
- •14.5. Loop
- •14.6. Особые условия
- •Глава 15. Пример: логический вывод
- •15.1. Цель
- •15.2. Сопоставление
- •15.3. Отвечая на запросы
- •15.4. Анализ
- •Глава 16. Пример: генерация HTML
- •16.1. HTML
- •16.2. Утилиты HTML
- •16.3. Утилита для итерации
- •16.4. Генерация страниц
- •Глава 17. Пример: объекты
- •17.1. Наследование
- •17.2. Множественное наследование
- •17.3. Определение объектов
- •17.4. Функциональный синтаксис
- •17.5. Определение методов
- •17.6. Экземпляры
- •17.7. Новая реализация
- •17.8. Анализ
- •Комментарии
- •Алфавитный указатель
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. Благо даря этому вместо: