- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
3.16. Мусор |
69 |
3.16. Мусор
Операции со списками могут быть довольно медленными по ряду при чин. Доступ к определенному элементу списка осуществляется не непо средственно по его номеру, а путем последовательного перебора всех предшествующих ему элементов, что может быть существенно медлен нее, особенно для больших списков. Так как список является набором вложенных ячеек, то для доступа к элементу приходится выполнить несколько переходов по указателям, в то время как для доступа к эле менту массива достаточно просто увеличить позицию указателя на за данное число. Впрочем, намного более затратным может быть выделе ние новых ячеек.
Автоматическое управление памятью – одна из наиболее ценных осо бенностей Лиспа. Лисп-система работает с сегментом памяти, называе мым куча (heap). Система владеет информацией об использованной и не использованной памяти и выделяет последнюю для размещения новых объектов. Например, функция cons выделяет память под создаваемую ею ячейку, поэтому создание новых объектов часто называют consing.
Если память будет выделяться, но не освобождаться, то рано или позд но свободная память закончится, и Лисп прекратит работу. Поэтому не обходим механизм поиска и освобождения участков кучи, которые бо лее не содержат нужных данных. Память, которая становится ненуж ной, называется мусором и подлежит уборке. Этот процесс называется
сборкой мусора (garbge collection, GC).
Как появляется мусор? Давайте немного намусорим:
>(setf lst (list ’a ’b ’c)) (A B C)
>(setf lst nil)
NIL
Первоначально мы вызвали list, которая трижды вызывала cons, а она, в свою очередь, выделила новые cons-ячейки в куче. После этого мы сде лали lst пустым списком. Теперь ни одна из трех ячеек, созданных cons, не может быть использована1.
Объекты, которые никаким образом не могут быть доступны для исполь зования, и считаются мусором. Теперь система может безбоязненно по вторно использовать память, выделенную cons, по своему усмотрению.
Такой механизм управления памятью – огромный плюс для програм миста, ведь теперь он не должен заботиться о явном выделении и осво бождении памяти. Соответственно исчезают баги, связанные с некор
1Это не всегда так. В toplevel доступны глобальные переменные *, **, ***, кото рым автоматически присваиваются последние три вычисленных значения. До тех пор пока значение связано с одной из вышеупомянутых переменных, мусором оно не считается.
70 |
Глава 3. Списки |
ректным управлением памятью. Утечки памяти и висячие указатели просто невозможны в Лиспе.
Разумеется, за удобство надо платить. Автоматическое управление па мятью работает во вред неаккуратному программисту. Затраты на рабо ту с кучей и уборку мусора иногда списывают на consing. Это имеет под собой основания, поскольку, за исключением случаев, когда программа ничего не выбрасывает, большинство выделенных cons-ячеек в конце концов окажутся мусором. И беда в том, что работа с consing может быть довольно затратной по сравнению с обычными операциями в програм ме. Конечно, прогресс не стоит на месте, автоматическое управление памятью становится более эффективным и алгоритмы по сборке мусора совершенствуются, но consing всегда будет иметь определенную стои мость, довольно существенную в некоторых реализациях языка.
Будучи неаккуратным, легко написать программу, выделяющую чрез мерно много памяти. Например, remove копирует ячейки, создавая но вый список, чтобы не вызвать побочный эффект. Тем не менее такого копирования можно избежать, используя деструктивные операции, которые модифицируют существующие данные, а не создают новые. Деструктивные функции подробно рассмотрены в разделе 12.4.
Тем не менее возможно написание программ, которые вообще не выде ляют память в процессе выполнения. Часто программа сначала пишет ся в чисто функциональном стиле с использованием большого количест ва списков, а по мере развития копирующие функции заменяются на деструктивные в критических к производительности участках кода. Но в этом случае сложно давать конкретные советы, потому что некоторые современные реализации Лиспа управляют памятью настолько хоро шо, что иногда выделение новой памяти может быть эффективнее, чем использование уже существующей. Более детально этот вопрос рассмат ривается в разделе 13.4.
Во всяком случае, consing подходит для прототипов и экспериментов. А если вы воспользуетесь гибкостью, которую дают списки, при изна чальном написании программы, то у вас больше шансов, что она дожи вет до более поздних этапов, на которых понадобится оптимизация.
Итоги главы
1.Cons-ячейка – это структура, состоящая из двух объектов. Списки состоят из связанных между собой ячеек.
2.Предикат equal менее строг, чем eql. Фактически он возвращает ис тину, если объекты печатаются одинаково.
3.Все объекты в Лиспе ведут себя как указатели. Вам никогда не при дется управлять указателями явно.
4.Скопировать список можно с помощью copy-list, объединить два спи ска – с помощью append.
Упражнения |
71 |
5.Кодирование повторов – простой алгоритм сжатия списков, легко реализуемый в Лиспе.
6.Common Lisp имеет богатый набор средств для доступа к элементам списков, и эти функции определены в терминах car и cdr.
7.Отображающие функции применяют определенную функцию по следовательно к каждому элементу или каждому хвосту списка.
8.Операции с вложенными списками сродни операциям с бинарными деревьями.
9.Чтобы оценить корректность рекурсивной функции, достаточно убе диться, что она соответствует нескольким требованиям.
10.Списки могут рассматриваться как множества. Для работы с множе ствами в Лиспе есть ряд встроенных функций.
11.Аргументы по ключу не являются обязательными и определяются не положением, а по соответствующей метке.
12.Список – подтип последовательности. Common Lisp имеет множест во функций для работы с последовательностями.
13.Cons-ячейка, не являющаяся правильным списком, называется то чечной парой.
14.С помощью списков точечных пар можно представить элементы ото бражения. Такие списки называются ассоциативными.
15.Автоматическое управление памятью освобождает программиста от ручного выделения памяти, но большое количество мусора может замедлить работу программы.
Упражнения
1.Представьте следующие списки в виде ячеек:
(a)(a b (c d))
(b)(a (b (c (d))))
(c)(((a b) c) d)
(d)(a (b . c) . d)
2.Напишите свой вариант функции union, который сохраняет порядок следования элементов согласно исходным спискам:
>(new-union ’(a b c) ’(b a d)) (A B C D)
3.Напишите функцию, определяющую количество повторений (с точ ки зрения eql) каждого элемента в заданном списке и сортирующую их по убыванию встречаемости:
>(occurrences ’(a b a d a c d c a))
((A . 4) (C . 2) (D . 2) (B . 1))
4. Почему (member ’(a) ’((a) (b))) возвращает nil?
72 |
Глава 3. Списки |
5.Функция pos+ принимает список и возвращает новый, каждый эле мент которого увеличен по сравнению с исходным на его положение в списке:
> (pos+ ’(7 5 1 4)) (7 6 3 7)
Определите эту функцию с помощью: (a) рекурсии, (b) итерации и
(c)mapcar.
6.После долгих лет раздумий государственная комиссия приняла по становление, согласно которому cdr указывает на первый элемент списка, а car – на его остаток. Определите следующие функции, удов летворяющие этому постановлению:
(a)cons
(b)list1
(c)length (для списков)
(d)member (для списков, без ключевых параметров)
7.Измените программу на рис. 3.6 таким образом, чтобы она создавала меньшее количество ячеек. (Подсказка: используйте точечные пары.)
8.Определите функцию, печатающую заданный список в точечной но тации:
> (showdots ’(a b c)) (A . (B . (C . NIL))) NIL
9.Напишите программу, которая ищет наиболее длинный путь в сети, не содержащий повторений (раздел 3.15). Сеть может содержать цик лы.
1Задачу 6b пока что не получится решить с помощью имеющихся у вас зна ний. Вам потребуется остаточный аргумент (&rest), который вводится на стр. 114. На эту оплошность указал Рикардо Феррейро де Оливьера. – Прим. перев.