- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
120 |
Глава 6. Функции |
Более того, несколько замыканий могут использовать одну и ту же пе ременную. Ниже определены две функции, использующие переменную counter:
(let ((counter 0)) (defun reset ()
(setf counter 0)) (defun stamp ()
(setf counter (+ counter 1))))
Такая пара функций может использоваться для создания меток време ни. При каждом вызове stamp мы получаем число, на единицу большее, чем предыдущее. Вызывая reset, мы обнуляем счетчик:
> (list (stamp) (stamp) (reset) (stamp)) (1 2 0 1)
Несомненно, можно сделать то же самое с использованием глобальной переменной, однако последняя не защищена от воздействий извне.
В Common Lisp существует встроенная функция complement, принимаю щая предикат и возвращающая противоположный ему. Например:
> (mapcar (complement #’oddp) ’(1 2 3 4 5 6))
(NIL T NIL T NIL T)
Благодаря замыканиям написать такую функцию очень просто:
(defun our-complement (f) #’(lambda (&rest args)
(not (apply f args))))
Если вы отвлечетесь от этого маленького, но замечательного примера, то обнаружите, что он – лишь вершина айсберга. Замыкания являются одной из уникальных черт Лиспа. Они открывают путь к новым воз можностям в программировании, не достижимым в других языках.°
6.6. Пример: строители функций
Язык Dylan известен как гибрид Scheme и Common Lisp, использующий синтаксис Pascal.° Он имеет большой набор функций, которые возвра щают функции. Помимо complement, рассмотренной нами в предыдущем разделе, Dylan включает compose, disjoin, conjoin, curry, rcurry и always. На рис. 6.2 приведены реализации этих функций в Common Lisp, а на рис. 6.3 показаны эквиваленты, следующие из этих определений.
Первая из них, compose, принимает одну или несколько функций и воз вращает новую функцию, которая применяет их по очереди. Таким об разом,
(compose #’a #’b #’c)
6.6. Пример: строители функций |
121 |
|
|
|
|
|
(defun compose (&rest fns) |
|
|
(destructuring-bind (fn1 . rest) (reverse fns) |
|
|
#’(lambda (&rest args) |
|
|
(reduce #’(lambda (v f) (funcall f v)) |
|
|
rest |
|
|
:initial-value (apply fn1 args))))) |
|
|
(defun disjoin (fn &rest fns) |
|
|
(if (null fns) |
|
|
fn |
|
|
(let ((disj (apply #’disjoin fns))) |
|
|
#’(lambda (&rest args) |
|
|
(or (apply fn args) (apply disj args)))))) |
|
|
(defun conjoin (fn &rest fns) |
|
|
(if (null fns) |
|
|
fn |
|
|
(let ((conj (apply #’conjoin fns))) |
|
|
#’(lambda (&rest args) |
|
|
(and (apply fn args) (apply conj args)))))) |
|
|
(defun curry (fn &rest args) |
|
|
#’(lambda (&rest args2) |
|
|
(apply fn (append args args2)))) |
|
|
(defun rcurry (fn &rest args) |
|
|
#’(lambda (&rest args2) |
|
|
(apply fn (append args2 args)))) |
|
|
(defun always (x) #’(lambda (&rest args) x)) |
|
|
|
|
Рис. 6.2. Компоновщики функций из Dylan
cddr = |
(compose #’cdr #’cdr) |
nth = |
(compose #’car #’nthcdr) |
atom = |
(compose #’not #’consp) |
= |
(rcurry #’typep ’atom) |
<= = |
(disjoin #’< #’=) |
listp = |
(disjoin #’null #’consp) |
=(rcurry #’typep ’list) 1+ = (curry #’+ 1)
=(rcurry #’+ 1)
1- = (rcurry #’- 1)
mapcan = (compose (curry #’apply #’nconc) #’mapcar) complement = (curry #’compose #’not)
Рис. 6.3. Некоторые эквиваленты
122 |
Глава 6. Функции |
возвращает функцию, эквивалентную вызову:
#’(lambda (&rest args) (a (b (apply #’c args))))
Из приведенного примера следует, что последняя функция может при нимать любое количество аргументов, в то время как остальные долж ны принимать ровно один.
Давайте создадим функцию, вычисляющую квадратный корень числа, округляющую результат и возвращающую его, заключенным в список:
> (mapcar (compose #’list #’round #’sqrt) ’(4 9 16 25))
((2) (3) (4) (5))
Следующие две функции, disjoin и conjoin, принимают некоторый на бор предикатов. Вызов disjoin возвращает предикат, возвращающий ис тину, если хотя бы один из заданных предикатов является истинным. Функция conjoin возвращает предикат, который будет истинным лишь в случае соответствия проверяемого аргумента всем заданным преди катам.
> |
(mapcar |
(disjoin #’integerp #’symbolp) |
|
|
|
’(a "a" 2 |
3)) |
(T NIL T |
T) |
|
|
> |
(mapcar |
(conjoin #’integerp #’oddp) |
|
|
|
’(a "a" 2 |
3)) |
(NIL NIL NIL T)
Рассматривая предикаты как множества, можно сказать, что disjoin возвращает объединение множеств, а conjoin – их пересечение.
Функции curry и rcurry («right curry») имеют общую идею с определен ной в предыдущем разделе make-adder. Они обе принимают функцию и некоторые ее аргументы и возвращают функцию, которая ожидает получить лишь остающиеся аргументы. Каждое из следующих выра жений равносильно (make-adder 3):
(curry #’+ 3) (rcurry #’+ 3)
Разница между curry и rcurry станет заметна на примере функции, раз личающей свои аргументы. Если мы применим curry к –, то полученная функция, будет вычитать свой аргумент из заданного числа:
>(funcall (curry #’- 3) 2)
1
вто время как для rcurry полученная функция будет вычитать задан ное число из своего аргумента:
>(funcall (rcurry #’- 3) 2)
-1
Наконец, always имеет свой аналог в Common Lisp – функцию constanly. Она принимает любой аргумент и возвращает функцию, всегда возвра
6.7. Динамический диапазон |
123 |
щающую этот аргумент. Как и identity, она может использоваться в тех случаях, когда требуется передать аргумент-функцию.
6.7. Динамический диапазон
В разделе 2.11 было показано различие между локальными и глобальны ми переменными. В действительности, различают лексические перемен ные, которые имеют лексический диапазон, и специальные переменные, имеющие динамический диапазон.1 На практике локальные перемен ные почти всегда являются лексическими, а глобальные переменные – специальными, поэтому обе пары терминов можно считать взаимозаме няемыми.
Внутри лексического диапазона символ будет ссылаться на перемен ную, которая имеет такое же имя в контексте, где появляется этот сим вол. Локальные переменные по умолчанию имеют лексическое окруже ние. Итак, если мы определим функцию в окружении, содержащем пе ременную x:
(let ((x 10)) (defun foo ()
x))
то символ x, используемый в функции, будет ссылаться на эту перемен ную независимо от того, была ли в окружении, из которого функция вызывалась, переменная x, имеющая другое значение:
> (let ((x 20)) (foo)) 10
В динамическом же диапазоне используется то значение переменной, которое имеется в окружении, где вызывается функция, а не в окруже нии, где она была определена.° Чтобы заставить переменную иметь ди намический диапазон, нужно объявить ее как special в том контексте, где она встречается. Если мы определим foo следующим образом:
(let ((x 10)) (defun foo ()
(declare (special x)) x))
то переменная x больше не будет ссылаться на лексическую перемен ную, существующую в контексте, в котором функция была определена, а будет ссылаться на любую динамическую переменную x, которая су ществует в момент вызова функции:
> (let ((x 20))
(declare (special x))
1Под диапазоном (scope) следует понимать область видимости; соответственно лексическая область видимости и динамическая область видимости. –
Прим. науч. ред.