- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
8
Символы
С Лисп-символами вы уже довольно хорошо знакомы, но есть еще неко торые вещи, на которые стоит обратить внимание. Поначалу не стоило углубляться в механизм их реализации. Вы можете использовать их как в качестве объектов, так и в качестве имен для объектов, не задумы ваясь о том, каким образом связаны эти две роли. Но в какой-то момент полезно остановиться и разобраться с тем, что же происходит внутри. В этой главе подробно обсуждаются все детали, связанные с символами.
8.1. Имена символов
В главе 2 символы описывались как имена переменных, существующие сами по себе. Однако область применения символов в Лиспе шире, чем область применения переменных в большинстве других языков. На са мом деле, имя символа – это обычная строка. Имя символа можно по лучить с помощью symbol-name:
> (symbol-name ’abc) "ABC"
Обратите внимание, что имена символов записываются в верхнем реги стре. По умолчанию Common Lisp не чувствителен к регистру, поэтому при чтении он преобразует все буквы в именах к заглавным:
>(eql ’aBc ’Abc)
T
>(CaR ’(a b c))
A
Для работы с символами, названия которых содержат пробелы или дру гие знаки, влияющие на алгоритм считывания, используется особый синтаксис. Любая последовательность знаков между вертикальными
8.2. Списки свойств |
145 |
черточками, считается символом. Таким образом вы можете поместить любые знаки в имя символа:
> (list ’|Lisp 1.5| ’|| ’|abc| ’|ABC|) (|Lisp 1.5| || |abc| ABC)
При считывании такого символа преобразование к верхнему регистру не производится, а макрознаки читаются как есть.
Так на какие же символы можно ссылаться, не прибегая к использова нию вертикальной черты? По сути, на все символы, имена которых не являются числом и не содержат знаков, имеющих для read специально го значения. Чтобы узнать, может ли символ существовать без такого оформления, достаточно просто посмотреть, как Лисп его напечатает. Если при печати символ не будет обрамлен вертикальными черточка ми, как последний символ в предыдущем примере, значит и вам не нужно их использовать.
Следует помнить, что вертикальные черточки – специальный синтак сис, а не часть имени символа:
> (symbol-name ’|a b c|) "a b c"
(Если вы желаете включить черточку в имя символа, расположите пе ред ней знак «\».)
8.2.Списки свойств
ВCommon Lisp каждый символ имеет собственный список свойств (property-list, plist). Функция get принимает символ и ключ, возвращая значение, связанное с этим ключом, из списка свойств:
> (get ’alizarin ’color) NIL
Для сопоставления ключей используется eql. Если указанное свойство не задано, get возвращает nil.
Чтобы ассоциировать значение с ключом, можно использовать setf вме сте с get:
>(setf (get ’alizarin ’color) ’red) RED
>(get ’alizarin ’color)
RED
Теперь свойство color (цвет) символа alizarin имеет значение red (крас ный).
Функция symbol-plist возвращает список свойств символа:
>(setf (get ’alizarin ’transparency) ’high) HIGH
>(symbol-plist ’alizarin)
(TRANSPARENCY HIGH COLOR RED)
146 |
Глава 8. Символы |
Заметьте, что списки свойств не представляются как ассоциативные списки, хотя и работают похожим образом.
В Common Lisp списки свойств используются довольно редко. Они в зна чительной мере вытеснены хеш-таблицами.
8.3. А символы-то не маленькие
Символы создаются неявным образом, когда мы печатаем их имена,
ипри отображении самих символов их имена – это все что мы видим. При таких обстоятельствах легко подумать, что символ – это лишь имя
иничего более. Однако многое скрыто от наших глаз.
Из того, как мы видим и используем символы, может показаться, что это маленькие объекты, такие же как, скажем, целые числа. На самом деле, символы обладают внушительными размерами и более походят на структуры, определяемые через defstruct. Символ может иметь имя, пакет, значение связанной с ним переменной, значение связанной функ ции и список свойств. Устройство символа и взаимосвязь между его компонентами показаны на рис. 8.1.
Мало кто использует настолько большое количество символов, чтобы вставал вопрос об экономии памяти. Однако стоит держать в уме, что символы – полноценные объекты, а не просто имена. Две переменные могут ссылаться на один символ, так же как и на один список: в таком случае они имеют указатель на общий объект.
|
name |
"FOO" |
|
package |
|
|
value |
27 |
|
function |
#<function> |
package |
plist |
(color red) |
|
|
Рис. 8.1. Структура символа
8.4.Создание символов
Вразделе 8.1 было показано, как получать имена символов. Также воз можно и обратное действие – преобразование строки в символ. Это бо лее сложная задача, потому что для ее выполнения необходимо иметь представление о пакетах.
8.5. Использование нескольких пакетов |
147 |
Логически пакеты – это таблицы, отображающие имена в символы. Лю бой символ принадлежит конкретному пакету. Символ, принадлежа щий пакету, называют интернированным в него. Пакеты делают воз можной модульность, ограничивая область видимости символов. Имена функций и переменных считаются символами, поэтому они могут быть доступны в соответствующих пакетах.
Большинство символов интернируются во время считывания. Когда вы впервые вводите символ, Лисп создает новый символьный объект и ин тернирует его в текущий пакет (по умолчанию это common-lisp-user). Од нако вы можете сделать то же самое вручную с помощью intern:
> (intern "RANDOM-SYMBOL") RANDOM-SYMBOL
NIL
Функция intern принимает также дополнительный аргумент – пакет (по умолчанию используется текущий) . Приведенный выше вызов соз дает в текущем пакете символ с именем "RANDOM-SYMBOL", если такого символа пока еще нет в данном пакете. Второй аргумент возвращает ис тину, когда такой символ уже существует.
Не все символы являются интернированными. Иногда может оказать ся полезным использование неинтернированных символов, так же как иногда лучше записывать номера телефонов не в записную книжку, а на отдельный клочок бумаги. Неинтернированные символы создают ся с помощью gensym. Мы познакомимся с ними при разборе макросов
вглаве 10.
8.5.Использование нескольких пакетов
Большие программы часто разделяют на несколько пакетов. Если каж дая часть программы расположена в собственном пакете, то разработ чик другой ее части может смело использовать имена функций и пере менных, имеющиеся в первой части.
При использовании языков, не поддерживающих разделение простран ства имен, программистам, работающим над серьезными проектами, приходится вводить договоренности, чтобы избежать конфликтов при использования одних и тех же имен. Например, разработчик подсисте мы отображения, вероятно, будет использовать имена, начинающиеся с disp_, в то время как программист, разрабатывающий математиче ские процедуры, будет применять имена, начинающиеся с math_. Так, например, функция, выполняющая быстрое преобразование Фурье, вполне может носить имя math_fft.
Пакеты выполняют эту работу самостоятельно. Внутри отдельного па кета можно использовать любые имена. Только те символы, которые бу дут явным образом экспортированы, будут видимы в других пакетах, где будут доступны с помощью префикса (или квалифицированы), соот ветствующего имени содержащего их пакета.