- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
190 |
Глава 11. CLOS |
Если задано необязательное свойство :documentation, то оно должно со держать строку описания данного слота. Задавая :type, вы обязуетесь обеспечить то, что слот будет содержать лишь значения заданного типа. Декларации типов будут рассмотрены в разделе 13.3.
11.4. Суперклассы
Второй аргумент defclass является списком суперклассов. От них класс наследует набор слотов. Так, если мы определим класс screen-circle как подкласс circle и graphic:
(defclass graphic ()
((color :accessor graphic-color :initarg :color) (visible :accessor graphic-visible :initarg :visible
:initform t)))
(defclass screen-circle (circle graphic)
())
то экземпляры screen-circle будут иметь четыре слота, по два от каждо го класса-родителя. И при этом не нужно создавать какие-либо новые слоты как его собственные – подкласс screen-circle создается лишь для объединения возможностей двух классов circle и graphic.
Параметры слотов (:accessor, :initarg и другие) наследуются вместе с са мими слотами и работают так же, как если бы они использовались для экземпляров circle или graphic:
> (graphic-color (make-instance ’screen-circle
:color ’red :radius 3))
RED
Тем не менее в определении класса можно указывать некоторые пара метры для наследуемых слотов, например :initform:
(defclass screen-circle (circle graphic) ((color :initform ’purple)))
Теперь экземпляры screen-circle будут пурпурными по умолчанию:
> (graphic-color (make-instance ’screen-color)) PURPLE
11.5. Предшествование
Мы познакомились с ситуацией, когда классы могут иметь несколько суперклассов. Если для некоторых суперклассов определен метод с од ним и тем же именем, Лисп должен выбрать один из них, руководству ясь правилом предшествования.
Для каждого класса существует список предшествования – порядок сле дования классов от наиболее специфичного до наименее специфичного, включая рассматриваемый класс. В примерах, приведенных ранее, по
11.5. Предшествование |
191 |
рядок предшествования был тривиальным, однако он может усложнить ся в больших программах. Вот пример более сложной иерархии классов:
(defclass sculpture () (height width depth)) (defclass statue (sculpture) (subject)) (defclas metalwork () (metal-type)) (defclass casting (metalwork) ())
(defclass cast-statue (statue casting) ())
Граф, представляющий класс cast-statue и его суперклассы, изображен на рис. 11.3.
|
t |
|
standard object |
sculpture |
metalwork |
statue |
casting |
cast statue
Рис. 11.3. Иерархия классов
Чтобы построить такой граф, необходимо начать с узла, соответствую щего классу, и двигаться снизу-вверх. Ребра, направленные вверх, свя зывают класс с прямыми суперклассами, которые располагаются слева- направо в соответствии с порядком соответствующих вызовов defclass. Эта процедура повторяется до тех пор, пока для каждого класса будет существовать лишь один суперкласс – standard-object. Это справедливо для классов, в определении которых второй аргумент был (). Узел, соот ветствующий каждому такому классу, связан с узлом standard-object, а он в свою очередь – с классом t. Проделав подобные операции, мы по лучим граф, изображенный на рис. 11.3.
Теперь мы можем составить список предшествования:
1.Начинаем с низа графа.
2.Движемся вверх, всегда выбирая левое из ранее непройденных ребер.
192 |
Глава 11. CLOS |
3.Если обнаруживается, что мы пришли к узлу, к которому ведет реб ро справа, то отматываем все перемещения до тех пор, пока не вер немся в узел с другими непройденными ребрами. Вернувшись, по вторяем шаг 2.
4.Завершаем процедуру по достижении t. Порядок, в котором каждый узел был впервые посещен, определяет список предшествования.
В пункте 3 упоминается важный момент: ни один класс не может нахо диться в списке предшествования до любого из его подклассов.
Стрелки на рис. 11.3 показывают направление обхода. Список предше ствования нашей иерархии будет следующим: cast-statue, statue, sculp ture, casting, metalwork, standard-object, t. Иногда положение класса в та ком списке называют его специфичностью. Классы расположены в нем по убыванию специфичности.
По списку предшествования можно определить, какой метод необходи мо применить при вызове обобщенной функции. Этот процесс рассмат ривается в следующем разделе. Порядок следования, связанный с вы бором наследуемого слота, нами пока не рассматривался. Соответст вующие правила его определения можно найти на стр. 428.°
11.6. Обобщенные функции
Обобщенной функцией называют функцию, построенную из одного или нескольких методов. Методы определяются через оператор defmethod, который напоминает defun:
(defmethod combine (x y) (list x y))
На данный момент combine имеет один метод, и ее вызов построит спи сок из двух аргументов:
> (combine ’a ’b) (A B)
Пока что мы не сделали ничего, с чем не справилась бы обычная функ ция. Однако отличия станут заметны при добавлении еще одного метода.
Для начала создадим несколько классов, с которыми будут работать на ши методы:
(defclass stuff () ((name :accessor name :initarg :name))) (defclass ice-cream (stuff) ())
(defclass topping (stuff) ())
Мы определили три класса: stuff, имеющий слот name, а также ice-cream и topping, являющиеся подклассами stuff.
Теперь создадим второй метод для combine:
(defmethod combine ((ic ice-cream) (top topping)) (format nil "~A ice-cream with ~A topping."
11.6. Обобщенные функции |
193 |
(name ic) (name top)))
В этом вызове defmethod параметры конкретизированы: каждый из них появляется в списке с именем класса. Конкретизации метода указыва ют на типы аргументов, к которым он применим. Приведенный метод может использоваться только с определенными аргументами: первым аргументом должен быть экземпляр ice-cream, а вторым – экземпляр topping.
Как Лисп определяет, какой метод вызвать? Из тех, что удовлетворяют ограничениям на классы, будет выбран наиболее специфичный. Это зна чит, например, что для аргументов, представляющих классы ice-cream и topping соответственно, будет применен последний определенный на ми метод:
> (combine (make-instance ’ice-cream :name ’fig) (make-instance ’topping :name ’treacle))
"FIG ice-cream with TREACLE topping."
Для любых других аргументов будет применен наш первый метод:
> (combine 23 ’skiddoo) (23 SKIDDOO)
Ни один из аргументов этого метода не был конкретизирован, поэтому он имеет наименьший приоритет и будет вызван лишь тогда, когда ни один другой метод не подойдет. Такие методы служат своего рода запас ным вариантом, подобно ключу otherwise в case-выражении.
Любая комбинация параметров метода может быть конкретизирована. В следующем методе ограничивается лишь первый аргумент:
(defmethod combine ((ic ice-cream) x) (format nil "~A ice-cream with ~A."
(name ic) x))
Если мы теперь вызовем combine с экземплярами ice-cream и topping, из двух последних методов будет выбран наиболее специфичный:
> (combine (make-instance ’ice-cream :name ’grape) (make-instance ’topping :name ’marshmallow))
"GRAPE ice-cream with MARSHMALLOW topping."
Однако если второй аргумент не принадлежит классу topping, будет вы зван только что определенный метод:
> (combine (make-instance ’ice-cream :name ’clam) ’reluctance)
"CLAM ice-cream with RELUCTANCE."
Аргументы обобщенной функции определяют набор применимых (app licable) методов. Метод считается применимым, если аргументы соот ветствуют спецификациям, налагаемым данным методом.
194 |
Глава 11. CLOS |
Отсутствие применимых методов приводит к ошибке. Если применим лишь один метод, он вызывается. Если применимо несколько методов, то в соответствии с порядком предшествования вызывается наиболее специфичный. Параметры сверяются слева направо. Если первый па раметр одного из применимых методов ограничен более специфичным классом, чем первый аргумент других методов, то он и считается наибо лее специфичным. Ничьи разрешаются переходом к следующему аргу менту, и т. д.1
В предыдущих примерах легко найти наиболее специфичный приме нимый метод, так как все объекты можно расположить по убыванию: экземпляр ice-cream принадлежит классам ice-cream, затем stuff, stan dard-object и, наконец, t.
Методы не обязаны использовать спецификаторы, являющиеся только классами, определенными через defclass. Они также могут применять типы (точнее классы, соответствующие встроенным типам) . Вот при мер метода combine с числовыми спецификаторами:
(defmethod combine ((x number) (y number)) (+ x y))
Кроме того, методы могут использовать отдельные объекты, сверяемые с помощью eql:
(defmethod combine ((x (eql ’powder)) (y (eql ’spark))) ’boom)
Спецификаторы индивидуальных объектов имеют больший приоритет, нежели спецификаторы классов.
Списки аргументов методов могут быть довольно сложны, как и спи ски аргументов обычных функций, однако они должны быть конгру энтны аргументам соответствующей обобщенной функции. Метод дол жен иметь столько же обязательных и необязательных параметров, сколько и обобщенная функция, и использовать либо &rest, либо &key, но не одновременно. Все приведенные ниже пары конгруэнтны:
(x)(a)
(x &optional y) (a &optional b)
(x |
y |
&rest z) |
(a |
b |
&key |
c) |
(x |
y |
&key z) |
(a |
b |
&key |
c d) |
а следующие пары – нет:
(x)(a b)
(x &optional y) (a &optional b c) (x &optional y) (a &rest b)
(x &key x y) (a)
1Мы не сможем дойти до конца аргументов и по-прежнему иметь ничью, по тому что тогда два метода будут подходить точно под одно и то же описание аргументов. Это невозможно, так как второе определение метода в этом слу чае просто затрет первое.
