- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
34 |
Глава 2. Добро пожаловать в Лисп |
1.Проверяем, пуст ли список lst. Если он пуст, значит, obj не присут ствует в списке.
2.Если obj является первым элементом lst, значит, он есть в этом спи ске.
3.В другом случае проверяем, есть ли obj среди оставшихся элементов списка lst.
Подобное описание порядка действий будет полезным, если вы захоти те понять, как работает рекурсивная функция.
Многим поначалу бывает сложно понять рекурсию. Причина этому – использование ошибочной метафоры для функций. Часто считается, что функция – это особый агрегат, который получает параметры в ка честве сырья, перепоручает часть работы другим функциям-агрегатам и в итоге получает готовый продукт – возвращаемое значение. При та ком рассмотрении возникает парадокс: как агрегат может перепору чать работу сам себе, если он уже занят?
Более подходящее сравнение для функции – процесс. Для процесса ре курсия вполне естественна. В повседневной жизни мы часто наблюдаем рекурсивные процессы и не задумываемся об этом. К примеру, предста вим себе историка, которому интересна динамика численности населе ния в Европе. Процесс изучения им соответствующих документов будет выглядеть следующим образом:
1.Получить копию документа.
2.Найти в нем информацию об изменении численности населения.
3.Если в нем также упоминаются иные источники, которые могут быть полезны, также изучить и их.
Этот процесс довольно легко понять, несмотря на то, что он рекурсивен, поскольку его третий шаг может повлечь за собой точно такой же про цесс.
Поэтому не стоит расценивать функцию our-member как некий агрегат, который проверяет присутствие какого-либо элемента в списке. Это всего лишь набор правил, позволяющих выяснить этот факт. Если мы будем воспринимать функции подобным образом, то парадокс, связан ный с рекурсией, исчезает.°
2.8. Чтение Лиспа
Определенная в предыдущем разделе функция заканчивается пятью закрывающими скобками. Более сложные функции могут заканчи ваться семью-восемью скобками. Это часто обескураживает людей, ко торые только начинают изучать Лисп. Как можно читать подобный код, не говоря уже о том, чтобы писать его самому? Как искать в нем парные скобки?
Ответ прост: вам не нужно делать это. Лисп-программисты пишут и чи тают код, ориентируясь по отступам, а не по скобкам. К тому же любой
2.9. Ввод и вывод |
35 |
хороший текстовый редактор, особенно если он поставляется с Лисп-сис темой, умеет выделять парные скобки. Если ваш редактор не делает это го, остановитесь и постарайтесь узнать, как включить подобную функ цию, поскольку без нее писать Лисп-код практически невозможно.1
С хорошим редактором поиск парных скобок в процессе написания ко да перестанет быть проблемой. Кроме того, благодаря общепринятым соглашениям касательно выравнивания кода вы можете легко читать код, не обращая внимания на скобки.
Любой Лисп-хакер, независимо от своего стажа, с трудом разберет опре деление our-member, если оно будет записано в таком виде:
(defun our-member (obj lst) (if (null lst) nil (if (eql (car lst) obj) lst (our-member obj (cdr lst)))))
С другой стороны, правильно выравненный код будет легко читаться даже без скобок:
defun our-member (obj lst) if (null lst)
nil
if eql (car lst) obj lst
our-member obj (cdr lst)
Это действительно удобный подход при написании кода на бумаге, в то время как в редакторе вы сможете воспользоваться удобной возможно стью поиска парных скобок.
2.9. Ввод и вывод
До сих пор мы осуществляли ввод-вывод с помощью toplevel. Чтобы ва ша программа была по-настоящему интерактивной, этого явно недоста точно. В этом разделе мы рассмотрим несколько функций ввода-вывода.
Основная функция вывода в Common Lisp – format. Она принимает два или более аргументов: первый определяет, куда будет напечатан ре зультат, второй – это строковый шаблон, а остальные аргументы – это объекты, которые будут вставляться в нужные позиции заданного шаб лона. Вот типичный пример:
> (format t "~A plus ~A equals ~A.~%" 2 3 (+ 2 3)) 2 plus 3 equals 5.
NIL
Обратите внимание, что здесь отображены два значения. Первая строка – результат выполнения format, напечатанный в toplevel. Обычно функции типа format не вызываются напрямую в toplevel, а используются внутри программ, поэтому возвращаемые ими значения не отображаются.
1В редакторе vi эта опция включается командой :set sm. В Emacs M-x lispmode будет отличным выбором.
36 |
Глава 2. Добро пожаловать в Лисп |
Первый аргумент для функции format, t, показывает, что вывод будет отправлен в стандартное место, принятое по умолчанию. Обычно это toplevel. Второй аргумент – это строка, служащая шаблоном для выво да. В ней каждое ~A определяет позицию для заполнения, а символ ~% соответствует переносу строки. Позиции по порядку заполняются зна чениями оставшихся аргументов.
Стандартная функция чтения – read (ee также называют считывателем) . Вызванная без аргументов, она выполняет чтение из стандартного мес та, которым обычно бывает toplevel. Ниже приведена функция, кото рая предлагает ввести любое значение и затем возвращает его:
(defun askem (string) (format t "~A" string) (read))
Это работает так:
> (askem "How old are you? ") How old are you? 29
29
Учтите, что для завершения считывания read будет ожидать нажатия вами Enter. Поэтому не стоит использовать функцию read, не печатая пе ред этим приглашение ко вводу, иначе может показаться, что програм ма зависла, хотя на самом деле она просто ожидает ввода.
Другая вещь, которую следует знать о функции read: это поистине мощ ный инструмент. Фактически это полноценный обработчик Лисп-выра жений. Она не просто читает символы и возвращает их в виде строки – она обрабатывает введенное выражение и возвращает полученный лис повый объект. В приведенном выше примере функция read возвращает число.
Несмотря на свою краткость, определение askem содержит нечто, чего мы до сих пор не встречали – тело функции состоит из нескольких вы ражений. Вообще говоря, оно может содержать любое количество выра жений. При вызове функции они вычисляются последовательно, и воз вращается значение последнего выражения.
Во всех предыдущих разделах мы придерживались «чистого» Лиспа, то есть Лиспа без побочных эффектов. Побочный эффект – это событие, которое каким-либо образом изменяет состояние системы. При вычис лении выражения (+ 1 2) возвращается значение 3, и никаких побоч ных эффектов не происходит. В последнем же примере побочный эф фект производится функцией format, которая не только возвращает значение, но и печатает что-то еще. Это одна из разновидностей побоч ных эффектов.
Если мы зададимся целью писать код без побочных эффектов, то будет бессмысленно определять функции, состоящие из нескольких выраже ний, так как в качестве значения функции будет использоваться значе ние последнего аргумента, а значения предыдущих выражений будут
2.10. Переменные |
37 |
утеряны. Если эти выражения не будут вызывать побочных эффектов, то вы даже не узнаете, вычислял ли их Лисп вообще.
2.10. Переменные
Один из наиболее часто используемых операторов в Common Lisp – это let, который позволяет вам ввести новые локальные переменные:
> (let ((x 1) (y 2)) (+ x y))
3
Выражение с использованием let состоит из двух частей. Первая содер жит инструкции, определяющие новые переменные. Каждая такая ин струкция содержит имя переменной и соответствующее ей выражение. Чуть выше мы создали две новые переменные, x и y, которым были при своены значения 1 и 2. Эти переменные действительны внутри тела let.
За списком переменных и значений следуют выражения, которые вы числяются по порядку. В нашем случае имеется только одно выраже ние, (+ x y). Значением всего вызова let будет значение последнего выра жения. Давайте напишем более избирательный вариант функции askem с использованием let:
(defun ask-number ()
(format t "Please enter a number. ") (let ((val (read)))
(if (numberp val) val (ask-number))))
Мы создаем переменную val, содержащую результат вызова read. Со храняя это значение, мы можем проверить, что было прочитано. Как вы уже догадались, numberp – это предикат, проверяющий, является ли его аргумент числом.
Если введенное значение – нечисло, то ask-number вызовет саму себя, что бы пользователь повторил попытку. Так будет повторяться до тех пор, пока функция read не получит число:
> (ask-number)
Please enter a number. a Please enter a number. (no hum) Please enter a number. 52
52
Переменные типа val, создаваемые оператором let, называются локаль ными, то есть действительными в определенной области. Есть также глобальные переменные, которые действительны везде.1
1Реальная разница между локальными и глобальными переменными будет пояснена в главе 6.