- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
182 |
Глава 10. Макросы |
(let ((val (calculate-something))) |
|
(if val |
|
(1+ |
val) |
0)) |
|
мы можем написать:
(aif (calculate-something) (1+ it)
0)
Разумное использование захвата переменных в макросах может оказать полезную услугу. Сам Common Lisp прибегает к такой возможности в не которых случаях, например в next-method-p и call-next-method.
Разобранные в этом разделе макросы демонстрируют смысл фразы «про граммы, которые пишут программы». Единожды определенный for по зволяет избежать написания множества однотипных выражений с do. Есть ли смысл в написании макроса просто для экономии на вводе? Од нозначно. Ровно по этой же причине мы пользуемся языками програм мирования, заставляя компиляторы самостоятельно генерировать ма шинный код, вместо того чтобы набирать его вручную. Макросы по зволяют вам дать своим программам те же преимущества, которые вы сокоуровневые языки дают программированию в целом. Аккуратное применение макросов позволит существенно сократить размер вашей программы, облегчая тем самым ее написание, понимание и поддержку.
Если у вас все еще остались какие-либо сомнения, представьте ситуа цию, когда встроенные макросы недоступны и код, в который они рас крывались бы, необходимо набирать вручную. Теперь подойдите к во просу с другой стороны. Представьте, что вы пишете программу, содер жащую множество сходных частей. Задайте себе вопрос: «А не пишу ли я результаты раскрытия макросов?» Если так, то макросы, генерирую щие этот код, – вот то, что вам нужно писать в действительности.
10.8. На Лиспе
Теперь, когда мы узнали, что такое макросы, мы видим, что даже боль шая часть самого Лиспа, чем мы предполагали, написана на самом Лиспе с помощью макросов. Большинство операторов в Common Lisp, которые не являются функциями, – это макросы, и все они написаны на Лиспе. И лишь 25 встроенных операторов являются специальными.
Джон Фодераро назвал Лисп «программируемым языком программи рования».° С помощью функций и макросов Лисп может быть превра щен в практически любой другой язык. (Подобный пример вы найдете в главе 17.) Каким бы ни был наилучший путь написания программы, можете быть уверены, что сможете подогнать Лисп под него.
Макросы являются одним из ключевых ингредиентов гибкости Лиспа. Они позволяют не просто изменять язык до неузнаваемости, но и делать
Итоги главы |
183 |
это понятным и эффективным способом. Интерес к макросам внутри Лисп-сообщества со временем только растет. Очевидно, что с их помо щью уже сейчас можно делать удивительные вещи, но многие из них еще только предстоит обнаружить. Вам, например, если захотите. В ру ках программиста Лисп непрерывно эволюционирует. Именно поэтому он до сих пор жив.
Итоги главы
1.Вызов eval – один из способов использовать списки как код, но он не эффективен и не нужен.
2.Чтобы определить макрос, вам необходимо описать то, во что он бу дет раскрываться. На самом деле, макросы – это функции, возвра щающие выражения.
3.Тело макроса, определенное с помощью обратной кавычки, выгля дит как результат его раскрытия.
4.Разработчик макроса должен помнить о захвате переменных и по вторных вычислениях. Чтобы протестировать макрос, полезно изу чить результат его раскрытия, напечатанный с помощью pprint.
5.Проблема повторных вычислений возникает в большинстве макро сов, раскрывающихся в вызовы setf.
6.Макросы более гибки по сравнению с функциями и позволяют соз давать более широкий круг утилит. Вы даже можете извлекать поль зу из возможности захвата переменных.
7.Лисп дожил до настоящих дней лишь потому, что эволюционирует в руках программиста. Это возможно в первую очередь благодаря макросам.
Упражнения
1.Пусть x = a, y = b, z = (c d). Запишите выражения с обратной кавыч кой, содержащие только заданные переменные (x, y и z) и приводя щие к следующим результатам:
(a)((C D) A Z)
(b)(X B C D)
(c)((C D A) Z)
2.Определите if через cond.
3.Определите макрос, аргументами которого являются число n и сле дующие за ним произвольные выражения. Макрос должен возвра щать значение n-го выражения:
> (let ((n 2))
(nth-expr n (/ 1 0) (+ 1 2) (/ 1 0)))
3
184 |
Глава 10. Макросы |
4.Определите ntimes (стр. 175), раскрывающийся в (локальную) рекур сивную функцию вместо вызова do.
5.Определите макрос n-of, принимающий на вход число n и выраже ние и возвращающий список значений, полученных последователь ным вычислением этого выражения для возрастающего n раз значе ния переменной:
> (let ((i 0) (n 4)) (n-of n (incf i)))
(1 2 3 4)
6.Определите макрос, принимающий список переменных и тело кода. По завершении выполнения кода переменные должны принять свои исходные значения.
7.Что не так со следующим определением push?
(defmacro push (obj lst)
‘(setf ,lst (cons ,obj ,lst)))
Приведите пример ситуации, в которой данный код будет работать не так, как настоящий push.
8. Определите макрос, удваивающий свой аргумент:
> (let ((x 1)) (double x) x)
2