- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
11.7. Вспомогательные методы |
195 |
Лишь обязательные параметры могут быть специализированы. Таким образом, метод полностью определяется именем и спецификациями обя зательных параметров. Если мы определим еще один метод с тем же именем и спецификациями, он заменит предыдущий. То есть установив:
(defmethod combine ((x (eql ’powder)) (y (eql ’spark))) ’kaboom)
мы тем самым переопределим поведение combine для аргументов powder
иspark.
11.7.Вспомогательные методы
Методы могут дополняться вспомогательными методами, включая be fore- (перед), after- (после) и around-методы (вокруг) . Before-методы по зволяют нам сказать: «Прежде чем приступить к выполнению, сделайте это». Они вызываются в порядке убывания специфичности, предваряя вызов основного метода. After-методы позволяют сказать: «P.S. А сде лайте заодно и это». Они вызываются после выполнения основного мето да в порядке увеличения специфичности. То, что выполняется между ними, ранее называлось нами просто методом, но более точно это – пер вичный метод. В результате вызова возвращается именно его значение, даже если после него были вызваны after-методы.
Before- и after-методы позволяют добавлять новое поведение к вызову первичного метода. Around-методы позволяют выполнять то же самое, но более радикальным образом. Если задан around-метод, он будет вы зван вместо первичного. Впрочем, по своему усмотрению он может вы звать первичный метод самостоятельно (с помощью функции call-next- method, которая предназначена именно для этой цели) .
Этот механизм называется стандартной комбинацией методов. Со гласно ему вызов обобщенной функции включает:
1.Вызов наиболее специфичного around-метода, если задан хотя бы один из них.
2.В противном случае по порядку:
(a)Все before-методы в порядке убывания специфичности.
(b)Наиболее специфичный первичный метод.
(c)Все after-методы в порядке возрастания специфичности.
Возвращаемым значением считается значение around-метода (в случае 1) или значение наиболее специфичного первичного метода (в случае 2).
Вспомогательные методы задаются указанием ключа-квалификатора (qualifier) после имени метода в определении defmethod. Если мы опреде лим первичный метод speak для класса speaker следующим образом:
(defclass speaker () ())
(defmethod speak ((s speaker) string)
196 |
Глава 11. CLOS |
(format t "~A" string))
то вызов speak для экземпляра speaker просто напечатает второй аргу мент:
> (speak (make-instance ’speaker) "I’m hungry")
I’m hungry NIL
Определяя подкласс intellectual, оборачивающий before- и after-мето ды вокруг первичного метода speak:
(defclass intellectual (speaker) ())
(defmethod speak :before ((i intellectual) string) (princ "Perhaps "))
(defmethod speak :after ((i intellectual) string) (princ " in some sense"))
мы можем создать подкласс говорунов, всегда добавляющих в конец и в начало исходной фразы несколько слов:
> (speak (make-instance ’intellectual) "I’m hungry")
Perhaps I’m hungry in some sense NIL
Как было упомянуто выше, вызываются все before- и after-методы. Ес ли мы теперь определим before- и after-методы для суперкласса speaker:
(defmethod speak :before ((s speaker) string) (princ "I think "))
то они будут вызываться из середины нашего «сэндвича»:
> (speak (make-instance ’intellectual) "I’m hungry")
Perhaps I think I’m hungry in some sense NIL
Независимо от того, вызываются ли before- и after-методы, при вызове обобщенной функции всегда возвращается значение первичного мето да. В нашем случае format возвращает nil.
Это утверждение не распространяется на around-методы. Они вызыва ются сами по себе, а все остальные методы вызываются, лишь если им позволит around-метод. Aroundили первичный методы могут вызывать следующий метод с помощью call-next-method. Перед этим уместно про верить наличие такого метода с помощью next-method-p.
С помощью around-методов вы можете определить другой, более преду предительный подкласс speaker:
(defclass courtier (speaker) ())
11.8. Комбинация методов |
197 |
(defmethod speak :around ((c courtier) string) |
|
(format t "Does the King believe that ~A? " |
string) |
(if (eql (read) ’yes) |
|
(if (next-method-p) (call-next-method)) |
|
(format t "Indeed, it is a preposterous |
idea.~%")) |
’bow) |
|
Когда первым аргументом speak является экземпляр класса courtier (придворный), языком придворного управляет around-метод:
>(speak (make-instance ’courtier) "kings will last") Does the King believe that kings will last? yes
I think kings will last BOW
>(speak (make-instance ’courtier) "the world is round") Does the King believe that the world is round? no Indeed, it is a preposterous idea.
BOW
Еще раз обратите внимание, что, в отличие от before- и after-методов, значением вызова обобщенной функции является возвращаемое значе ние around-метода.
11.8. Комбинация методов
Единственный первичный метод, который будет вызываться в стан дартной комбинации, является наиболее специфичным (при этом он может вызывать другие методы с помощью call-next-method). Но иногда нам хотелось бы иметь возможность комбинировать результаты всех подходящих по правилам выбора первичных методов.
Можно определить другие способы комбинации, например возврат сум мы всех применимых методов. Под операторной комбинацией методов понимается случай, когда вызов завершается вычислением некоторого Лисп-выражения, являющегося применением оператора к результатам вызовов применимых первичных методов в порядке убывания их спе цифичности. Если мы определим обобщенную функцию price, комби нирующую значения с помощью функции +, и при этом для price не су ществует применимых around-методов, то она будет вести себя, как ес ли бы она была определена следующим образом:
(defun price (&rest args) |
|
(+ (apply наиболее специфичный первичный метод |
args) |
. |
|
. |
|
. |
|
(apply наименее специфичный первичный метод |
args))) |
Если существуют применимые around-методы, то они вызываются в по рядке очередности, как в стандартной комбинации. В операторной ком бинации around-метод по-прежнему может использовать call-next-me thod, но уже не может вызывать первичные методы.
198 |
Глава 11. CLOS |
Используемый способ комбинации методов может быть задан в момент явного создания обобщенной функции через defgeneric:
(defgeneric price (x) (:method-combination +))
Теперь метод price будет использовать + для комбинации методов; вто рой аргумент defmethod-определения должен содержать +. Определим некоторые классы с ценами:
(defclass jacket () ()) (defclass trousers () ())
(defclass suit (jacket trousers) ())
(defmethod price + ((jk jacket)) 350) (defmethod price + ((tr trousers)) 200)
Теперь если мы запросим цену костюма (suit), то получим сумму приме нимых методов price:
> (price (make-instance ’suit)) 550
Параметр :method-combination, передаваемый вторым аргументом defge neric (а также вторым аргументом defmethod), может быть одним из сле дующих символов:
+ |
and |
append list |
max |
min |
nconc |
or progn |
Также можно использовать символ standard, который явным образом задает использование стандартной комбинации методов.
Определив единожды способ комбинации для обобщенной функции, вы вынуждены использовать этот способ для всех методов, имеющих то же имя. Попытка использовать другие символы (а также :before и :after) приведет к возникновению ошибки. Чтобы изменить способ комбина ции price, вам придется удалить саму обобщенную функцию с помо щью fmakunbound.
11.9. Инкапсуляция
Объектно-ориентированные языки часто дают возможность отличить внутреннее представление объекта от интерфейса, который они пред ставляют внешнему миру. Сокрытие деталей реализации несет в себе двойную выгоду: вы можете модифицировать реализацию объекта, не затрагивая интерфейс доступа к нему извне, а также предотвращаете потенциально опасные изменения объектов. Такое сокрытие деталей иногда называют инкапсуляцией.
Несмотря на то, что инкапсуляцию часто считают свойством объектно- ориентированного программирования, эти две идеи в действительно сти существуют независимо друг от друга. Мы уже знакомились с ин