- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
5
Управление
В разделе 2.2 впервые упоминается порядок вычисления, который те перь должен быть вам хорошо знаком. В данной главе будут рассмотре ны операторы, которые не подчиняются этому правилу, позволяя вам самостоятельно управлять ходом вычисления. Если обычные функции считать листьями Лисп-программы, то с помощью таких операторов можно создавать ее ветви.
5.1. Блоки
В Common Lisp есть три основных оператора для создания блоков кода: progn, block и tagbody. С первым из них мы уже знакомы. Выражения, со ставляющие тело оператора progn, вычисляются последовательно, при этом возвращается значение последнего:°
> (progn
(format t "a") (format t "b") (+ 11 12))
ab 23
Так как возвращается значение лишь последнего выражения, то ис пользование progn (или других операторов блоков) предполагает нали чие побочных эффектов.
Оператор block – это progn с именем и запасным выходом. Первый аргу мент должен быть символом, и он определяет имя блока. Находясь внут ри блока, вы можете в любой момент возвратить значение с помощью return-from с соответствующим именем блока:
> (block head
(format t "Here we go.")
96 Глава 5. Управление
(return-from head ’idea)
(format t "We’ll never see this. ")) Here we go.
IDEA
Вызов return-from позволяет вам внезапно, но изящно выйти из любого места в коде. Второй аргумент return-from служит в качестве возвращае мого значения из блока, имя которого передается в первом аргументе. Все выражения, находящиеся в соответствующем блоке после returnfrom, не вычисляются.
Кроме того, существует специальный макрос return, выполняющий вы ход из блока с именем nil:
> (block nil (return 27))
27
Многие операторы в Common Lisp, принимающие на вход блок кода, не явно оборачивают его в block с именем nil. В частности, так делают все итерационные конструкции:
> (dolist (x ’(a b c d e)) (format t "~A " x)
(if (eql x ’c) (return ’done)))
A B C
DONE
Тело функции, создаваемой defun, является блоком с тем же именем, что и сама функция:
(defun foo () (return-from foo 27))
Вне явного или неявного block ни return, ни return-from не работают.
С помощью return-from можно написать улучшенный вариант функции read-integer:
(defun read-integer (str) (let ((accum 0))
(dotimes (pos (length str))
(let ((i (digit-char-p (char str pos)))) (if i
(setf accum (+ (* accum 10) i)) (return-from read-integer nil))))
accum))
Предыдущий вариант read-integer (стр. 83) выполнял проверку всех зна ков до построения целого числа. Теперь же два шага собраны воедино, так как использование return-from позволяет прервать выполнение, ко гда мы встретим нечисловой знак.
Третий основной конструктор блоков, tagbody, допускает внутри себя использование оператора go. Атомы, встречающиеся внутри блока, рас
5.2. Контекст |
97 |
цениваются как метки, по которым может выполняться переход. Ниже приведен довольно корявый код для печати чисел от 1 до 10:
> (tagbody (setf x 0)
top
(setf x (+ x 1)) (format t "~A " x)
(if (< x 10) (go top))) 1 2 3 4 5 6 7 8 9 10
NIL
Оператор tagbody – один из тех, которые применяются для построения других операторов, но, как правило, не годятся для непосредственного использования. Большинство итеративных операторов построены по верх tagbody, поэтому иногда (но не всегда) есть возможноть использо вать внутри них метки и переход по ним с помощью go.
Как решить, какой из блоков использовать? Практически всегда подой дет progn. При желании иметь возможность экстренного выхода лучше использовать block. Практически никто не использует tagbody напрямую.
5.2.Контекст
Соператором let, позволяющим группировать выражения, мы уже зна комы. Помимо набора вычисляемых выражений он позволяет задавать новые переменные, которые действуют внутри его тела:
> (let ((x 7) (y 2))
(format t "Number") (+ x y))
Number 9
Операторы типа let создают новый лексический контекст (lexical context). В нашем примере лексический контекст имеет две новые пере менные, которые вне его становятся невидимыми.
Вызов let можно понимать как вызов функции. В разделе 2.14 показа но, что на функцию можно ссылаться не только по имени, но и по лямб да-выражению. Раз лямбда-выражение эквивалентно имени функции, то мы можем использовать его вместо имени, ставя первым элементом выражения:
> ((lambda (x) (+ x 1)) 3) 4
Пример с let, приведенный в начале раздела, может быть переписан как лямбда-вызов:
((lambda (x y) (format t "Number")
98 |
Глава 5. Управление |
(+ x y))
7
2)
Любой вопрос, возникающий у вас при использовании let, легко разре шится, если построить аналогичную конструкцию с помощью lambda.°
Приведем пример ситуации, которую разъясняет наша аналогия. Рас смотрим выражение let, в котором одна из переменных зависит от дру гой переменной той же let-конструкции:
(let ((x 2)
(y (+ x 1))) (+ x y))
Значение x в (+ x 1) не определено, и это видно из соответствующего лямбда-вызова:
((lambda (x y) (+ x y)) 2
(+ x 1))
Совершенно очевидно, что выражение (+ x 1) не может ссылаться на пе ременную x в лямбда-выражении.
Но как быть, если мы хотим, чтобы одна из переменных в let-вызове за висела от другой? Для этой цели существует оператор let*:
> (let* ((x 1)
(y (+ x 1))) (+ x y))
3
Вызов let* полностью эквивалентен серии вложенных let. Наш пример можно переписать так:
(let ((x 1))
(let ((y (+ x 1))) (+ x y)))
Как в let, так и в let* исходные значения переменных – nil. Если имен но эти значения вам нужны, можно не заключать объявления в скобки:
> (let (x y) (list x y))
(NIL NIL)
Макрос destructing-bind является обобщением let. Вместо отдельных переменных он принимает шаблон (pattern) – одну или несколько пере менных, расположенных в виде дерева, – и связывает эти переменные с соответствующими частями реального дерева. Например:
> (destructing-bind (w (x y) . z) ’(a (b c) d e) (list w x y z))
(A B C (D E))
В случае несоответствия шаблона дереву, переданному во втором аргу менте, возникает ошибка.