Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
910 lines (785 sloc) 84.7 KB
\chapter{Заключение: что дальше?}
\label{ch:32}
\thispagestyle{empty}
Я надеюсь, теперь вы убеждены, что в названии этой книги нет противоречия. Однако вполне
вероятно, что есть какая-то область программирования, которая очень практически важна для
вас и которую я совсем не обсудил. Например, я ничего не сказал о том, как разрабатывать
графический пользовательский интерфейс (GUI), как связываться с реляционными базами
данных, как разбирать XML или как писать программы, которые являются клиентами различных
сетевых протоколов. Также я не обсудил две темы, которые станут важными, когда вы начнёте
писать реальные приложения на языке Common Lisp: оптимизацию кода и сборку приложений для
поставки.
Должно быть очевидно, что я не собираюсь глубоко охватить эти темы в этой финальной
главе. Вместо этого я дам вам несколько направлений, в которых вы можете двигаться,
занимаясь теми аспектами программирования на Lisp, которые интересуют вас больше.
\section{Поиск библиотек Lisp}
В~то время как стандартная библиотека функций, типов данных и макросов, поставляемая с
языком Common Lisp, достаточно велика, она служит лишь целям общего
программирования. Специализированные задачи, такие как создание графического
пользовательского интерфейса (GUI), общение с базами данных и разбор XML, требуют
библиотек, не обеспечиваемых стандартом этого языка.
Простейшим путём найти библиотеку, делающую что-то нужное вам, может быть просто проверка
ее наличия в составе вашей реализации Lisp. Большинство реализаций предоставляет по,
крайней мере, некоторые возможности, не указанные в стандарте языка. Коммерческие
поставщики, как правило, особенно усиленно работают над предоставлением дополнительных
библиотек для своих реализаций с целью оправдать их стоимость. Например, Franz's Allegro
Common Lisp Enterprise Edition поставляется среди прочего с библиотеками для разбора XML,
общения по протоколу SOAP, генерации HTML, взаимодействия с реляционными базами данных
и построения графического интерфейса пользователя различными путями. Другая выдающаяся
коммерческая реализация, LispWorks, также предоставляет несколько подобных библиотек,
включая пользующуюся уважением переносимую библиотеку CAPI, которая может использоваться
для разработки GUI-приложений, работающих на любой операционной системе, где есть
LispWorks.
Свободные реализации и реализации с открытым кодом обычно не включают такую большую связку
библиотек, вместо этого полагаясь на переносимые свободные и открытые библиотеки. Но даже
эти реализации обычно заполняют такие из наиболее важных областей, не охваченных
стандартом языка, как работа с сетями и многопоточность.
Единственный недостаток использования специфичных для реализации библиотек~-- это то, что
они привязывают вас к той реализации, которая их предоставляет. Если вы поставляете
приложения конечным пользователям или развёртываете ваши приложения на серверах, которыми
вы сами управляете, это может быть не очень важно. Однако если вы хотите писать код для
повторного использования другими программистами на Lisp или просто не хотите быть
привязанными к конкретной реализации, это, может быть, будет вас раздражать.
Переносимые библиотеки (переносимость означает либо что они написаны целиком на
стандартном Common Lisp, либо что они содержат необходимые макросы, чтобы работать с
несколькими реализациями)\footnote{Сочетание средств выборочного чтения исходного кода
(\lstinline!#+!, \lstinline!#-!) и макросов даёт возможность разрабатывать для
нестандартных средств слои переносимости, которые ничего не делают, кроме предоставления
общего API, развёрнутого поверх специфичного API конкретной реализации. Переносимая
библиотека файловых путей из главы~\ref{ch:15}~-- пример библиотеки такого рода, хотя
она, скорее, служит не для устранения различий в API разных реализаций, а для сглаживания
различий в интерпретации стандарта языка авторами разных реализаций.}\hspace{\footnotenegspace} лучше всего искать
в Интернете.
Вот три лучших места, откуда можно начать поиски (с обычными предосторожностями,
связанными с тем, что все URL устаревают сразу же, как только они напечатаны на бумаге):
\begin{itemize}
\item \pclURL{http://www.common-lisp.net/}{Common-Lisp.net}~-- сайт, размещающий свободные
и открытые проекты на Common Lisp, предоставляющий средства контроля версий исходного кода,
списки рассылки и размещение веб-документов проектов. В~первые полтора года работы сайта
было зарегистрировано около сотни проектов.
\item \pclURL{http://clocc.sourceforge.net/}{Коллекция открытого кода Common Lisp (The
Common Lisp Open Code Collection, CLOCC)}~-- немного более старая коллекция библиотек
свободного ПО, которые, как подразумевается, должны быть переносимы между разными
реализациями языка Common Lisp и самодостаточными, то есть не зависящими от каких-то
других библиотек, не включённых в эту коллекцию.
\item \pclURL{http://www.cliki.net/}{Cliki (Common Lisp Wiki)}~-- Wiki-сайт, посвящённый
свободному программному обеспечению на языке Common Lisp. Так же как и любой другой
сайт, основанный на Wiki, он может изменяться в любое время, обычно на нем есть довольно
много ссылок на библиотеки и реализации Common Lisp с открытым кодом. Система
редактирования документов, на которой работает сайт и давшая ему имя, также написана на
языке Common Lisp.
\end{itemize}
Пользователи Linux, работающие на системах Debian или Gentoo, также могут легко
устанавливать постоянно растущее число библиотек для языка Lisp, которые распространяются
с помощью средств распространения и установки этих систем: apt-get на Debian и emerge на
Gentoo.
Я не буду сейчас рекомендовать какие-то конкретные библиотеки, поскольку си\-туа\-ция с ними
меняется каждый день~-- после многих лет зависти к библиотекам языков Perl, Python и
Java программисты на Common Lisp в последние пару лет приняли вызов к созданию такого
набора библиотек, которого заслуживает Common Lisp, и коммерческих, и с открытым кодом.
Одна из областей, в которой в последнее время было много активности,~-- это фронт
разработки графического интерфейса приложений. В~отличие от Java и C\#, но как и в языках
Perl, Python и C, в языке Common Lisp не существует единственного пути для разработки
графического интерфейса. Способ разработки зависит от реализации Common Lisp, с которой вы
работаете, а также от операционной системы, которую вы хотите поддерживать.
Коммерческие реализации Common Lisp обычно обеспечивают какой-то способ разработки
графического интерфейса для платформ, на которых они работают. В~дополнение к этому
LispWork предоставляет библиотеку CAPI, уже упоминавшуюся, для разработки переносимых
графических приложений.
Из программного обеспечения с открытым кодом у вас есть несколько возможных вариантов. На
системах Unix вы можете писать низкоуровневые приложения для системы X Windows, используя
библиотеку CLX, реализацию X Windows протокола на чистом языке Common Lisp, примерно такую
же, как xlib для языка C. Или вы можете использовать различные обёртки для высокоуровневых
API и библиотек, таких как GTK или Tk, так же, как вы можете делать это в языках Perl или
Python.
Или если вы ищете что-то совсем необычное, то можете посмотреть на библиотеку Common
Lisp Interface Manager (CLIM). Будучи наследником графической библиотеки символических
LISP-машин, CLIM является мощным, но сложным средством. Хотя многие коммерческие
реализации языка Lisp поддерживают ее, не похоже, что она очень сильно использовалася. Но в
последние несколько лет новая реализация CLIM с открытым кодом, McCLIM, набирает обороты
(она располагается на сайте Common-Lisp.net), так что, возможно, мы на грани нового расцвета
этой библиотеки.
\section{Взаимодействие с другими языками программирования}
В~то время как много полезных библиотек может быть написано на чистом Common Lisp,
используя только возможности, указанные в стандарте языка, и гораздо больше может быть
написано с использованием нестандартных средств, предоставляемых какой-то из реализаций,
иногда проще использовать существующую библиотеку, написанную на другом языке, таком
как~C.
Стандарт языка не даёт механизма для вызова из кода на языке Lisp кода, написанного на
другом языке программирования, и не требует, чтобы реализация языка предоставляла такие
возможности. Но в наше время почти все реализации поддерживают то, что называется Foreign
Function Interface, или кратко~-- FFI\pclfootnote{Foreign Function Interface эквивалентен в
основном JNI в языке Java, XS в языке Perl или модулю расширения языка Python.}.
Основная задача FFI~-- позволить вам дать языку Lisp достаточно информации для того, чтобы
прилинковать чужой код, написанный не на Lisp. Таким образом, если вы собираетесь вызывать
функции из какой-то библиотеки для языка C, вам нужно рассказать языку Lisp о том, как
транслировать объекты Lisp, передаваемые этой функции, в типы данных языка C, а значение,
возвращаемое функцией,~-- обратно в объекты языка Lisp. Однако каждая реализация
предоставляет свой собственный механизм FFI, со своими отличными от других возможностями и
синтаксисом. Некоторые реализации FFI позволяют делать функции обратного вызова (callback
functions), другие~-- нет. Библиотека Universal Foreign Function Interface (UFFI)
предоставляет слой для переносимости приложений, написанных с использованием FFI,
доступный на более полудюжины реализаций Common Lisp. Библиотека определяет макросы,
которые раскрываются в вызовы соответствующих функций интерфейса FFI данной
реализации. Библиотека UFFI применяет подход <<наименьшего общего знаменателя>>, то есть
она не может использовать преимущества всех возможностей разных реализаций FFI, но все же
обеспечивает хороший способ построения обёрток API для языка С\pclfootnote{Два главных
недостатка библиотеки UFFI~-- это отсутствие поддержки вызова Lisp-функций из кода на
языке C, которую предоставляют многие, но не все FFI-реализации, и отсутствие поддержки
CLISP, одной из популярных реализаций Common Lisp, библиотека FFI которой хотя и
достаточно хороша, но сильно отличается от FFI других реализаций и поэтому не
вписывается легко в рамки модели UFFI.}.
\section{Сделать, чтобы работало; правильно, быстро}
Как уже было сказано много раз, по разным сведениям, Дональдом Кнутом, Чарльзом Хоаром и
Эдсгером Дейкстрой, <<преждевременная оптимизация является первопричиной всех
зол>>\pclfootnote{Кнут использовал эту фразу несколько раз в своих публикациях, включая его
работу 1974 года <<Программирование как искусство>> (<<Computer Programming as an
Art>>), удостоенную премии Тьюринга, и в работе <<Структурное программирование с
использованием оператора goto>> (<<Structured Programs with goto Statements.>>). В
работе <<Ошибки в TeX>> (<<The Errors of TeX>>) он приписывал эту фразу Чарльзу Хоару.
А Хоар в своём электронном письме от 2004 года к Гансу Генвицу из компании phobia.com
(Hans Genwitz of phobia.com) признался, что не помнит точно происхождения этой фразы, но
он бы приписал её авторство Дейкстре.}. Common Lisp~-- замечательный язык в этом
отношении, если вы хотите следовать этой практике и при этом вам нужна высокая
производительность. Эта новость может быть для вас сюрпризом, если вы уже слышали
традиционное мнение о том, что Lisp~-- медленный язык. В~ранние годы языка Lisp, когда
компьютеры ещё программировали с помощью перфокарт, этот язык с его высокоуровневыми
чертами был обречён быть медленнее, чем его конкуренты, а именно ассемблер и Фортран. Но
это было давно. В~то же время Lisp использовался для всего, от создания сложных систем
искусственного интеллекта до написания операционных систем, и было проделано много
работы, чтобы выяснить, как компилировать Lisp в эффективный код. В~этом разделе мы
поговорим о некоторых из причин, почему Common Lisp является прекрасным языком для
написания высокопроизводительных программ, и о том, как это достигается.
Первой причиной того, что Lisp является прекрасным языком для написания
высокопроизводительного кода, является, как ни парадоксально, динамический характер
программирования на этом языке~-- та самая вещь, которая сначала мешала довести
производительность программ на языке Lisp до уровней, достигнутых компиляторами языка
Фортран. Причина того, что динамичность Lisp упрощает создание высокопроизводительного
кода, заключается в том, что первый шаг к эффективному коду~-- это всегда поиск
правильных алгоритмов и структур данных.
Динамические свойства языка Lisp сохраняют код гибким, что упрощает опробование разных
подходов. Имея ограниченное время на создание программы, вы в конечном итоге более
вероятно придёте к более высокопроизводительной версии, если не будете проводить много
времени, забираясь в тупики и выбираясь из них обратно. В~языке Common Lisp вы можете
опробовать идею, увидеть, что она ведёт в никуда, и двигаться дальше, не тратя много
времени, убеждая компилятор, что ваш код все же достоин того, чтобы он наконец запустился,
и ожидая, когда же он наконец закончит его компилировать. Вы можете написать простую, но
эффективную версию функции~-- набросок кода,~-- чтобы определить, работает ли ваш
основной код правильно, а затем заменить эту функцию на более сложную и более эффективную
её реализацию, если все хорошо. Но если общий подход к решению оказался с изъяном, то вы
не потратите кучу времени на отладку функции, которая в конечном итоге не нужна, что
означает, что у вас остаётся больше времени для поиска лучшего подхода к решению задачи.
Следующая причина того, что Common Lisp является хорошим языком для разработки
высокопроизводительного программного обеспечения, заключается в том, что большинство
реализаций Common Lisp обладает зрелыми компиляторами, которые генерируют достаточно
эффективный машинный код. Мы остановимся сейчас на том, как помочь компиляторам
сгенерировать код, который был бы сравним с кодом, генерируемым компиляторами языка C, но
эти реализации и так уже немного быстрее, чем те языки программирования, реализации
которых менее зрелы и которые используют более простые компиляторы или
интерпретаторы. Также, поскольку компилятор Lisp доступен во время выполнения программы,
программист на Lisp имеет некие возможности, которые было бы трудно эмулировать в других
языках: ваша программа может генерировать код на Lisp во время выполнения, который затем
может быть скомпилирован в машинный код и запущен. Если сгенерированный код должен
работать много раз, то это большой выигрыш. Или, даже без использования компилятора во
время выполнения, замыкания дают вам другой способ объединять код и данные времени
выполнения. Например, библиотека регулярных выражений CL-PPCRE, работающая под CMUCL,
быстрее, чем библиотека регулярных выражений языка Perl, на некоторых тестах, несмотря
даже на то, что библиотека языка Perl написана на высокооптимизированном языке C. Это,
вероятно, происходит от того, что в Perl регулярные выражения транслируются в байт-код,
который затем интерпретируется средствами поддержки регулярных выражений Perl, в то время
как библиотека CL-PPCRE транслирует регулярные выражения в дерево скомпилированных
функций, использующих замыкания (closures), которые вызывают друг друга средствами
нормальных вызовов функций.
Однако даже с правильным алгоритмом и высококачественным компилятором вы можете не
достичь нужной вам скорости. Значит, пришло время подумать об оптимизации и
профайлере. Основной подход здесь в языке Lisp, как и в других языках,~-- сначала
применить профайлер, чтобы найти места, где ваша программа реально тратит много времени, а
уже потом озаботиться ускорением этих частей.
Есть несколько подходов к профилированию. Стандарт языка предоставляет несколько
простейших средств измерения времени выполнения каких-то форм. В~частности, макроcом
\lstinline{TIME} можно обернуть любую форму, и он вернёт возвращаемое формой значение,
напечатав сообщение в поток \lstinline!*TRACE_OUTPUT*! о том, как долго выполнялась форма
и сколько памяти она использовала. Конкретный вид сообщения определяется конкретной
реализацией.
Вы также можете использовать \lstinline{TIME} для довольно быстрого и грубого
профилирования, чтобы сузить ваши поиски узкого места. Например, предположим, у вас есть
долго работающая функция, которая вызывает две другие функции, примерно так:
\begin{myverb}
(defun foo ()
(bar)
(baz))
\end{myverb}
Если вы хотите увидеть, какая из функций работает дольше, то можете изменить определение
функции таким образом:
\begin{myverb}
(defun foo ()
(time (bar))
(time (baz)))
\end{myverb}
Теперь вы можете вызвать \lstinline{foo}, и Lisp напечатает два отчёта, один для \lstinline{bar},
другой для \lstinline{baz}. Формат отчёта зависит от реализации, вот как это выглядит в Allegro
Common Lisp:
\begin{myverb}
CL-USER> (foo)
; cpu time (non-gc) 60 msec user, 0 msec system
; cpu time (gc) 0 msec user, 0 msec system
; cpu time (total) 60 msec user, 0 msec system
; real time 105 msec
; space allocation:
; 24,172 cons cells, 1,696 other bytes, 0 static bytes
; cpu time (non-gc) 540 msec user, 10 msec system
; cpu time (gc) 170 msec user, 0 msec system
; cpu time (total) 710 msec user, 10 msec system
; real time 1,046 msec
; space allocation:
; 270,172 cons cells, 1,696 other bytes, 0 static bytes
\end{myverb}
Конечно, было бы немного проще это читать, если бы вывод включал какие-то пометки. Если вы
часто пользуетесь этим приёмом, было бы полезно определить ваш собственный макрос вот так:
\begin{myverb}
(defmacro labeled-time (form)
`(progn
(format *trace-output* "~2&~a" ',form)
(time ,form)))
\end{myverb}
Если вы замените \lstinline{TIME} на \lstinline{labeled-time} в \lstinline{foo}, вы увидите следующий
вывод:
\begin{myverb}
CL-USER> (foo)
(BAR)
; cpu time (non-gc) 60 msec user, 0 msec system
; cpu time (gc) 0 msec user, 0 msec system
; cpu time (total) 60 msec user, 0 msec system
; real time 131 msec
; space allocation:
; 24,172 cons cells, 1,696 other bytes, 0 static bytes
(BAZ)
; cpu time (non-gc) 490 msec user, 0 msec system
; cpu time (gc) 190 msec user, 10 msec system
; cpu time (total) 680 msec user, 10 msec system
; real time 1,088 msec
; space allocation:
; 270,172 cons cells, 1,696 other bytes, 0 static bytes
\end{myverb}
С этой формой отчёта сразу ясно, что большее время выполнения \lstinline{foo} тратится в
\lstinline{baz}.
Конечно, вывод от \lstinline{TIME} становится немного неудобным, если форма, которую вы хотите
профилировать, вызывается последовательно много раз. Вы можете создать свои средства
измерения, используя функции \lstinline{GET-INTERNAL-REAL-TIME} и \lstinline{GET-INTERNAL-RUN-TIME},
которые возвращают число, увеличиваемое на величину константы
\lstinline{INTERNAL-TIME-UNITS-PER-SECOND} каждую секунду.
\lstinline{GET-INTERNAL-REAL-TIME} измеряет абсолютное время, реальное время, прошедшее между
событиями, в то время как \lstinline{GET-INTERNAL-RUN-TIME} даёт некое специфичное для
конкретной реализации значение, равное времени, которое Lisp-программа тратит реально на
выполнение именно пользовательского кода, исключая внутренние затраты Lisp-машины на
поддержку программы, такие как выделение памяти и сборка мусора.
Вот достаточно простой, но полезный вспомогательный инструмент профилирования, построенный
с помощью нескольких макросов и функции \lstinline{GET-INTERNAL-RUN-TIME}:
\begin{myverb}
(defparameter *timing-data* ())
(defmacro with-timing (label &body body)
(with-gensyms (start)
`(let ((,start (get-internal-run-time)))
(unwind-protect (progn ,@body)
(push (list ',label ,start (get-internal-run-time)) *timing-data*)))))
(defun clear-timing-data ()
(setf *timing-data* ()))
(defun show-timing-data ()
(loop for (label time count time-per %-of-total) in (compile-timing-data) do
(format t "~3d% ~a: ~d ticks over ~d calls for ~d per.~%"
%-of-total label time count time-per)))
(defun compile-timing-data ()
(loop with timing-table = (make-hash-table)
with count-table = (make-hash-table)
for (label start end) in *timing-data*
for time = (- end start)
summing time into total
do
(incf (gethash label timing-table 0) time)
(incf (gethash label count-table 0))
finally
(return
(sort
(loop for label being the hash-keys in timing-table collect
(let ((time (gethash label timing-table))
(count (gethash label count-table)))
(list label time count (round (/ time count)) (round (* 100 (/ time total))))))
#'> :key #'fifth))))
\end{myverb}
Этот профайлер позволяет вам обернуть вызов любой формы в макрос
\lstinline{with-timing}. Каждый раз, когда форма будет выполняться, время начала и конца
выполнения будет записываться в список, связываясь с меткой, которую вы
указываете. Функция \lstinline{show-timing-data} выводит таблицу, в которой показано, как много
времени было потрачено в различно помеченных секциях кода, следующим образом:
\begin{myverb}
CL-USER> (show-timing-data)
84% BAR: 650 ticks over 2 calls for 325 per.
16% FOO: 120 ticks over 5 calls for 24 per.
NIL
\end{myverb}
Очевидно, вы могли бы сделать этот профайлер более сложным во многих отношениях. С другой
стороны, каждая реализация Lisp часто предоставляет свои средства профилирования, которые
могут выдавать информацию, часто недоступную пользовательскому коду, поскольку они имеют
доступ к внутренним механизмам реализации.
Как только вы нашли узкое место в вашем коде, вы начинаете оптимизацию. Первое, что вам
следует попробовать,~-- это, конечно, попытаться найти более эффективный базовый алгоритм,
что всегда приносит большую выгоду. Но если предположить, что вы уже используете
эффективный алгоритм, то тогда как раз время начинать рихтовать код, то есть
оптимизировать код, чтобы он не делал абсолютно ничего, кроме необходимой работы.
Главное средство при этом~-- дополнительные объявления Lisp. Основная идея объявлений в
языке Lisp~-- в том, что они дают компилятору информацию, которую он может использовать
различным образом, чтобы генерировать более хороший код.
Например, рассмотрим простую функцию:
\begin{myverb}
(defun add (x y) (+ x y))
\end{myverb}
Как я упоминал в главе~\ref{ch:10}, если вы сравните производительность этой Lisp-функции
с вроде бы эквивалентной функцией на языке C:
\begin{lstlisting}[language=C]
int add (int x, int y) { return x + y; }
\end{lstlisting}
\noindent{}вы, возможно, обнаружите, что Lisp-функция заметно медленнее, даже если ваша
реализация Common Lisp обладает высококачественным компилятором в машинный код.
Это происходит потому, что версия функции на Common Lisp выполняет гораздо больше
всего~-- компилятор Common Lisp даже не знает, что значения \lstinline{x} и \lstinline{y} являются
числами, и таким образом вынужден сгенерирвать код для проверки типов во время выполнения.
И как только он определил, что это~-- числа, он должен также определить, какого типа эти
числа~-- целые, рациональные, с плавающей точкой или комплексные, и перенаправить вызов
соответствующей вспомогательной процедуре, обрабатывающей соответствующие типы. И даже
если \lstinline{x} и \lstinline{y} являются целыми числами, то процедура сложения должна
позаботиться о том, что результат сложения может быть слишком большим для представления в
FIXNUM-числе, числе, которое может быть представлено в одном машинном слове, и, таким
образом, должна выделить число типа BIGNUM.
В~языке C, с другой стороны, поскольку типы всех переменных объявлены, компилятор знает
точно, какой тип значений будет храниться в \lstinline{x} и \lstinline{y}. И, поскольку арифметика
языка C просто вызывает переполнение, когда результат сложения слишком большой, чтобы быть
представленным в возвращаемом типе, в функции нет ни проверки на переполнение, ни
выделения длинного целого значения, если результат не вмещается в машинное слово.
Таким образом, поскольку поведение кода на Common Lisp гораздо более близко к
математической корректности, версия на языке C, возможно, может быть скомпилирована в одну
или две машинные инструкции. Но если вы пожелаете дать компилятору Common Lisp ту же
информацию, которую имеет компилятор языка C о типах аргументов и возвращаемом значении, и
принять некоторые компромисы, подобные используемым в языке C, относительно общности кода
и проверки ошибок, функция на Common Lisp также может компилироваться в пару машинных
инструкций.
Именно для этого и служат объявления. Главная польза объявлений~-- в том, чтобы сказать
компилятору о типах переменных и других выражений. Например, вы можете указать
компилятору, что складываемые аргументы являются FIXNUM-значениями, написав функцию
следующим образом:
\begin{myverb}
(defun add (x y)
(declare (fixnum x y))
(+ x y))
\end{myverb}
Выражение \lstinline{DECLARE} не является формой языка Common Lisp, это~-- часть
синтаксиса макроса \lstinline{DEFUN}. Если это выражение используется, то оно должно быть
указано до кода из которого состоит тело функции\footnote{Объявления могут появляться в
большинстве форм, которые вводят новые переменные, как, например \lstinline{LET},
\lstinline{LET*} и семейство описывающих циклы макросов \lstinline{DO}. \lstinline{LOOP}
имеет свой механизм объявления типов переменных цикла. Специальный оператор
\lstinline{LOCALLY}, упоминаемый в главе~\ref{ch:20}, делает не что иное, как создаёт
место для объявлений.}\hspace{\footnotenegspace}. Данное объявление объявляет, что
аргументы функции \lstinline{x} и \lstinline{y} будут всегда FIXNUM-значениями. Другими
словами, это обещание компилятору, и компилятору разрешено генерировать код в
предположении, что все, что вы пообещали ему, будет истинным.
Чтобы объявить тип возвращаемого значения, вы можете обернуть основное выражение функции
\lstinline{(+ x y)} в специальный оператор \lstinline{THE}. Этот оператор принимает спецификатор
типа, такой как \lstinline{FIXNUM}, и какую-то форму, и говорит компилятору, что эта форма
будет возвращать значение указанного типа. Таким образом, чтобы дать компилятору Common
Lisp всю ту же информацию, которую имеет компилятор языка C, вы можете написать следующее:
\begin{myverb}
(defun add (x y)
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
Однако даже эта версия нуждается в ещё одном объявлении, чтоб дать компилятору Common Lisp
те же разрешения, что есть и у компилятора языка C, генерировать быстрый, но опасный
код. Объявление \lstinline{OPTIMIZE} используется для того, чтобы указать компилятору, как
распределить своё внимание между пятью целями:
\begin{itemize}
\item скоростью генерируемого кода;
\item количеством проверок времени выполнения;
\item использованием памяти как в терминах размера кода, так и в терминах размера памяти
данных;
\item количеством отладочной информации, хранимой вместе с кодом;
\item скоростью процесса компиляции.
\end{itemize}
Объявление \lstinline{OPTIMIZE} содержит один или более списков, каждый из которых содержит
один из символов: \lstinline{SPEED}, \lstinline{SAFETY}, \lstinline{SPACE}, \lstinline{DEBUG} или
\lstinline{COMPILATION-SPEED} и число от нуля до трёх включительно. Число указывает
относительный вес, который компилятор должен дать соответствующему параметру, причём 3
означает наиболее важное направление, а 0~-- что это не имеет значения вообще. Таким
образом, чтобы заставить компилятор Common Lisp компилировать функцию \lstinline{add}
более-менее так же, как это бы делал компилятор языка C, вы можете переписать её так:
\begin{myverb}
(defun add (x y)
(declare (optimize (speed 3) (safety 0)))
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
Конечно, теперь Lisp-версия функции страдает многими из слабостей C-версии: если
переданные аргументы не FIXNUM-значения или если сложение вызывает переполнение,
результаты будут математически некорректны или даже хуже. Также если кто-то вызовет
функцию \lstinline{add} с неправильным количеством параметров, будет мало хорошего. Таким
образом, вам следует использовать объявления этого вида только после того, как ваша
программа стала работать правильно, и вы должны добавлять их только в местах, где
профилирование показывает необходимость этого. Если вы имеете достаточную
производительность без этих объявлений, пропускайте их. Но если профайлер показывает вам
какое-то проблемное место в вашем коде и вам нужно оптимизировать его~-- вперёд. Поскольку
вы можете использовать объявления таким образом, редко ког\-да нужно переписывать код на
языке C только из соображений производительности. Для доступа к существующему коду на C
используется FFI, но когда нужна производительность, подобная языку C, используют
объявления. И конечно, то, как близко вы захотите приблизить производительность данной
части кода на языке Common Lisp к коду на C или C++, зависит главным образом от вашего
желания.
Другое средство оптимизации, встроенное в язык Lisp,~-- это функция
\lstinline{DISASSEMBLE}. Точное поведение данной функции зависит от реализации, потому что оно
зависит от того, как реализация компилирует код: в машинный код, байт-код или какую-то
другую форму. Но основная идея в том, что она показывает вам код, сгенерированный
компилятором, когда он компилировал данную функцию.
Таким образом, вы можете использовать \lstinline{DISASSEMBLE}, чтобы увидеть, возымели ли ваши
объявления какой-то эффект на генерируемый код. Если ваша реализация языка Lisp использует
компиляцию в машинный код и если вы знаете язык ассемблера вашей платформы, вы сможете
достаточно хорошо представить себе, что реально происходит, когда вы вызываете одну из
ваших функций. Например, вы можете использовать \lstinline{DISASSEMBLE}, чтобы понять, в чем
различия между нашей первой версией \lstinline{add} и окончательной версией. Сначала определите
и скомпилируйте исходную версию
\begin{myverb}
(defun add (x y) (+ x y))
\end{myverb}
Затем в сессии REPL вызовите \lstinline{DISASSEMBLE} с именем этой функции. В~Allegro это
выведет следующий ассемблероподобный листинг сгенерированного компилятором кода:
\begin{myverb}
CL-USER> (disassemble 'add)
;; disassembly of #<Function ADD>
;; formals: X Y
;; code start: #x737496f4:
0: 55 pushl ebp
1: 8b ec movl ebp,esp
3: 56 pushl esi
4: 83 ec 24 subl esp,$36
7: 83 f9 02 cmpl ecx,$2
10: 74 02 jz 14
12: cd 61 int $97 ; SYS::TRAP-ARGERR
14: 80 7f cb 00 cmpb [edi-53],$0 ; SYS::C_INTERRUPT-PENDING
18: 74 02 jz 22
20: cd 64 int $100 ; SYS::TRAP-SIGNAL-HIT
22: 8b d8 movl ebx,eax
24: 0b da orl ebx,edx
26: f6 c3 03 testb bl,$3
29: 75 0e jnz 45
31: 8b d8 movl ebx,eax
33: 03 da addl ebx,edx
35: 70 08 jo 45
37: 8b c3 movl eax,ebx
39: f8 clc
40: c9 leave
41: 8b 75 fc movl esi,[ebp-4]
44: c3 ret
45: 8b 5f 8f movl ebx,[edi-113] ; EXCL::+_2OP
48: ff 57 27 call *[edi+39] ; SYS::TRAMP-TWO
51: eb f3 jmp 40
53: 90 nop
; No value
\end{myverb}
Очевидно, что здесь полно всякой всячины. Если вы знакомы с ассемблером процессоров
архитектуры x86, вы, может быть, это поймёте. Теперь скомпилируйте версию функции \lstinline{add}
со всеми объявлениями:
\begin{myverb}
(defun add (x y)
(declare (optimize (speed 3) (safety 0)))
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
И дизассемблируйте функцию \lstinline{add} снова, и посмотрите, был ли какой-то толк от
этих объявлений.
\begin{myverb}
CL-USER> (disassemble 'add)
;; disassembly of #<Function ADD>
;; formals: X Y
;; code start: #x7374dc34:
0: 03 c2 addl eax,edx
2: f8 clc
3: 8b 75 fc movl esi,[ebp-4]
6: c3 ret
7: 90 nop
; No value
\end{myverb}
Похоже, что был.
\section{Поставка приложений}
Другая практически важная тема, о которой я не говорил нигде в этой книге,~-- это как
поставлять приложения, написанные на языке Lisp. Главная причина, по которой я пренебрегал
этой темой, заключается в том, что есть много разных способов это делать, и который из них
лучше вам подходит, зависит от того, какой вид программного обеспечения вам нужно
распространять, кому его надо распространять и какой реализацией Common Lisp этот кто-то
пользуется. В~этом разделе я дам обзор некоторых из возможностей.
Если вы написали код, которым хотите поделиться со своими коллегами, которые тоже пишут на
языке Lisp, самый простой путь распространения~-- это распространять исходный
код\footnote{Файлы FASL, получающиеся после компиляции с помощью \lstinline{COMPILE-FILE},
имеют формат, зависящий от реализации, и могут даже быть несовместимы с разными версиями
одной и той же реализации. Таким образом, они~-- не очень хороший способ распространения
кода на языке Lisp. В~одном случае они могут быть удобны~-- как способ обеспечения
исправлений вашего приложения, которое работает на какой-то определённой версии
конкретной реализации. Тогда, чтобы исправить ваше приложение, достаточно загрузить
FASL-файл с помощью \lstinline{LOAD}, и, поскольку FASL-файл может содержать любой код, он
также может быть использован как для определения новой версии кода, так и для изменения
существующих данных, чтобы они соответствовали новой версии.}\hspace{\footnotenegspace}. Вы можете поставлять
простую библиотеку как один исходный файл, который программисты могут загрузить в их образ
Lisp-машины командой \lstinline{LOAD}, возможно, после компиляции с помощью
\lstinline{COMPILE-FILE}.
Более сложные библиотеки или приложения, разбитые на несколько исходных файлов, ставят
дополнительный вопрос: для того чтобы загрузить сложный код, файлы исходного кода должны
быть загружены и откомпилированы в правильном порядке. Например, файл, содержащий
определения макросов, должен быть загружен до файлов, которые используют эти макросы, а
файл, содержащий инструкцию определения пакета \lstinline{DEFPACKAGE}, должен быть загружен до
того, как все файлы, использующие этот пакет, будут просто читаться
\lstinline{READ}'ом. Программисты на языке Lisp называют это проб\-ле\-мой определения систем
(system definition problem) и обычно разрешают её с по\-мощью средств, называемых средствами
определения систем (system definition facilities) или утилитами определения систем (system
definition utilities), которые являются чем-то вроде аналогов билд-системам, таким как
утилиты \lstinline{make} и \lstinline{ant}. Как и эти средства, средства определения систем
позволяют указать зависимости между разными файлами и берут на себя заботу о загрузке и
компиляции этих файлов в правильном порядке, стараясь при этом выполнять только ту работу,
которая необходима, например перекомпилировать только те файлы, которые изменились.
В~наши дни наиболее широко используется система определения систем, называемая
\lstinline{ASDF}, что означает Another System Definition Facility (еще одно средство
определения систем)\footnote{\lstinline{ASDF} был написан Дэниэлем Барлоу (Daniel Barlow),
одним из разработчиков \lstinline{SBCL}, и был включён в \lstinline{SBCL} как часть, а
также поставлялся как самостоятельная библиотека. В~последнее время он был заимствован и
включён в другие реализации Lisp, такие как \lstinline{OpenMCL} и
\lstinline{Allegro}.}\hspace{\footnotenegspace}. Основная идея \lstinline{ASDF} в том, что вы описываете вашу
систему в специальном файле~-- ASD, а \lstinline{ASDF} предоставляет возможности по
выполнению некоторого набора операций с описанными таким образом системами, такие как
загрузка и компиляция систем. Система может быть объявлена зависимой от других систем,
которые в таком случае будут корректно загружены при необходимости. Например, вот как
выглядит содержимое файла <<html.asd>>, включающего описание системы для \lstinline{ASDF}
для библиотеки \lstinline{FOO} из глав~\ref{ch:31} и~\ref{ch:32}:
\begin{myverb}
(defpackage :com.gigamonkeys.html-system (:use :asdf :cl))
(in-package :com.gigamonkeys.html-system)
(defsystem html
:name "html"
:author "Peter Seibel <peter@gigamonkeys.com>"
:version "0.1"
:maintainer "Peter Seibel <peter@gigamonkeys.com>"
:license "BSD"
:description "HTML and CSS generation from sexps."
:long-description ""
:components
((:file "packages")
(:file "html" :depends-on ("packages"))
(:file "css" :depends-on ("packages" "html")))
:depends-on (:macro-utilities))
\end{myverb}
Если вы добавите символьную ссылку на этот файл в каталог, указанный в переменной
\lstinline{asdf:*central-registry*}\footnote{В~системе Windows, где нет символьных ссылок, это
надо делать немного по-другому, но принцип тот же. (\emph{Примечание переводчика}: на самом
деле в современном Win32 API поддерживаются символьные ссылки в файловой системе NTFS,
правда, операции с ними недоступны в большинстве стандартного ПО для работы с файлами, но
предоставляются такой известной программой, как \lstinline{Far}. Во времена создания этой
книги данная возможность уже существовала, но, видимо, автор не был с нею знаком ввиду
малораспространённости средств, поддерживающих её. Кроме того, символьные ссылки в
Unix/Lunux-образных операционных системах имеют некоторые особенности, в результате
которых пути к файлам относительно символьной ссылки вычисляются с использованием того
места, куда эта ссылка ссылается. В~Win32 такого не происходит, поэтому этот способ для
аналогичных целей просто неприменим в Win32.)}\hspace{\footnotenegspace}, то затем вы можете набрать следующую
комманду:
\begin{myverb}
(asdf:operate 'asdf:load-op :html)
\end{myverb}
\noindent{}чтобы скомпилировать и загрузить файлы <<packages.lisp>>, <<html.lisp>> и
<<html-macros.lisp>> в правильном порядке после того, как система \lstinline{:macro-utilities}
будет скомпилирована и загружена. Примеры других файлов определения систем \lstinline{ASDF} вы
можете найти в прилагаемом к книге коде~-- код из каждой практической главы определён как
система с соответствующими межсистемными зависимостями, определёнными в формате ASDF.
Большинство свободного ПО и ПО с открытым кодом на языке Common Lisp, которое вы найдёте,
будет поставляться с ASD-файлом. Некоторые библиотеки могут поставляться с другими
системами определения, такими как немного более старая \lstinline{MK:DEFSYSTEM}, или даже
системами, изобретёнными авторами этой библиотеки, но общая тенденция, кажется, все же в
использовании \lstinline{ASDF}\footnote{Другое средство, \lstinline{ASDF-INSTALL},
построенное поверх систем \lstinline{ASDF} и \lstinline{MK:DEFSYSTEM}, предоставляет
простой способ автоматически скачать библиотеки из Интернета и загрузить их. Лучший
способ начать знакомство с \lstinline{ASDF-INSTALL}~-- инструкция, написанная Эди
Вайтзом (Edi Weitz) <<A tutorial for ASDF-INSTALL>> (\url{http://
www.weitz.de/asdf-install/}).}\hspace{\footnotenegspace}.
Конечно, \lstinline{ASDF} позволяет программистам легко устанавливать библиотеки, но это никак
не помогает поставлять приложения конечным пользователям, которые не имеют представления о
языке Lisp. Если вы поставляете приложения для конечного пользователя, по-видимому, вы бы
хотели предоставить пользователям нечто, что он бы мог загрузить, установить и запустить,
не зная ничего о языке Lisp. Вы не можете ожидать, что пользователи будут отдельно
устанавливать реализацию Lisp'а, и вы бы, наверное, хотели, чтобы приложения, написанные на
языке Lisp, запускались так же, как и все прочие приложения в вашей операционной
системе,~-- двойным нажатием кнопки мыши на иконке приложения или набором имени приложения
в командной строке интерпретатора команд.
Однако, в отличие от программ, написанных на языке C, которые обычно могут расчитывать на
присутствие неких совместно используемых библиотек, которые составляют так называемый
<<С-рантайм>> и присутствуют как часть операционной системы, программы на языке Lisp
должны включать ядро языка Lisp, то есть ту же часть Lisp-системы, которая используется
при разработке, хотя и, возможно, без каких-то модулей, не требуемых во время выполнения
программы.
И даже все ещё более сложно~-- понятие <<программа>> не очень хорошо определено в языке
Lisp. Как вы видели во время чтения этой книги, процесс разработки программ на языке
Lisp~-- это инкрементальный процес, подразумевающий постоянные изменения определений и
данных, находящихся внутри исполняемого образа Lisp-машины. Поэтому <<программа>>~-- это
всего лишь определённое состояние этого образа, которое достигнуто в результате загрузки
файлов \lstinline{.lisp} или \lstinline{.fasl}, которые содержат код, который создаёт
соответствующие определения и данные. Вы могли бы соответственно распространять приложение
на языке Lisp как рантайм Lisp-машины, плюс набор файлов \lstinline{.fasl} и исполняемый
модуль, который запускал бы рантайм Lisp-машины, загружал бы все файлы \lstinline{.fasl} и
как-то вызывал бы соответствующую начальную функцию вашего приложения. Однако, поскольку в
реальности загрузка файлов \lstinline{.fasl} может занимать значительное время, особенно если
они должны выполнить какие-то вычисления для установки начального состояния данных,
большинство реализаций Common Lisp предоставляет способ выгрузить исполняемый образ
Lisp-машины в файл, называемый \emph{image file} (\emph{файл образа}) или иногда
\emph{core file}, чтобы сохранить все состояние Lisp-машины. Когда рантайм Lisp-машины
запускается, первым делом он загружает файл образа, что у него получается гораздо быстрее,
чем если бы то же состояние восстанавливалось с помощью загрузки файлов \lstinline{.fasl}.
Обычно файл образа~-- это базовый образ Lisp-машины, содержащий только стандартные пакеты,
определённые стандартом языка, и какие-то дополнительные пакеты, предоставляемые данной
реализацией. Но в большинстве реализаций вы можете указать другой, свой файл образа. Таким
образом, вместо поставки приложения в виде рантайма Lisp-машины и набора файлов
\lstinline{.fasl} вы можете поставлять рантайм Lisp-машины и только один файл образа,
содержащий все определения кода и данных, которые составляют ваше приложение. В~таком
случае вам будет надо только запустить рантайм с этим файлом образа и вызвать функцию,
служащую точкой входа в ваше приложение.
Здесь всё становится зависящим от реализации и операционной системы. Некоторые реализации
Common Lisp, в особенности коммерческие, как, например, Allegro или LispWorks, предоставляют
средства для создания таких файлов образа. Например, Allegro Enterprise Edition
предоставляет функцию \lstinline{excl:generate-application}, которая создаёт каталог,
содержащий рантайм языка Lisp как разделяемую библиотеку, файл образа и выполняемый файл,
который запускает рантайм с данным образом. Похожим образом механизм LispWorks
Professional Edition, называемый <<поставка>> (delivery), позволяет вам строить
однофайловый исполняемый файл из ваших программ. В~различных реализациях Unix вы можете
делать фактически то же самое, хотя, возможно, там проще использовать командный файл для
запуска всего, чего угодно.
В~Mac OS X всё ещё лучше: поскольку все приложения в этой ОС упакованы в \texttt{.app}-пакеты,
которые, по сути, являются каталогами с определённой структурой, очень легко упаковать все
части приложения на Lisp в \texttt{.app}-пакет, который можно запустить двойным нажатием клавиши
мыши. Утилита Bosco Микеля Эвинса (Mikel Evins) делает создание \texttt{.app}-пакетов простым для
приложений, работающих на OpenMCL.
Конечно, в наши дни другой популярный способ поставки приложений~-- это серверные
приложения. Это та ниша, где Common Lisp может превосходно использоваться: вы можете
выбрать сочетание операционной системы и реализации Common Lisp, которая вас устраивает, и
вам не нужно заботиться о поставке приложения в том виде, в котором его мог бы установить
пользователь. А возможность языка Common Lisp интерактивно разрабатывать и отлаживать
программы позволяет вам отлаживаться и обновлять версии вашей программы без остановки
сервера, что либо вообще было бы невозможно в менее динамичных языках программирования,
либо требовало бы построения довольно сложной инфраструктуры поддержки.
\section{Что дальше?}
Ну вот и все. Добро пожаловать в чудесный мир языка Lisp. Лучшее, что вы можете сейчас
сделать (если вы уже это не сделали),~-- это начать писать свои собственные программы на
языке Lisp. Выберите проект, который вам интересен, и сделайте его в Common Lisp. Потом
сделайте ещё один. Как говорится, намылить, прополоскать, повторить...
Однако, если вам нужна дальнейшая помощь, этот раздел даст вам несколько мест, где её
можно найти. Для начинающих полезно посмотреть сайт книги <<Practical Common Lisp Web
site>> по ссылке \url{http://www.gigamonkeys.com/book}, где вы найдёте исходный код из
практических глав книги, список опечаток и ссылки на другие ресурсы в сети Интернет.
Вдобавок к сайтам, которые я упомянул в разделе <<Поиск библиотек Lisp>>, вы, возможно,
заходите изучить Common Lisp HyperSpec (известную также как HyperSpec или CLHS), которая
является HTML-версией ANSI-стандарта языка, приготовленной Кентом Питманом (Kent Pitman) и
сделанной общедоступной компанией LispWorks по ссылке
\url{http://www.lispworks.com/documentation/HyperSpec/index.html}. HyperSpec ни в коем
случае не является учебником, но это самое надёжное руководство по языку, которое вы
только можете достать, не покупая печатную версию стандарта языка у комитета ANSI, и при
том гораздо более удобное в повседневном использовании\pclfootnote{SLIME включает в свой
состав библиотеку на языке Elisp (Emacs lisp), которая позволяет вам автоматически
попадать по ключевому слову, определённому в стандарте, на статью в HyperSpec. Вы также
можете загрузить полную копию HyperSpec из Интернета и использовать её локально.}.
Если вы хотите связаться с другими лисперами, Usenet-группа (группа новостей)
<<comp.lang.lisp>> и IRC-канал <<\#lisp>> в \pclURL{http://www.freenode.net}{чат-сети
Freenode}~-- вот два основных сборища лисперов в сети\translationnote{Здесь сложно было
бы удержаться от упоминания русскоязычного канала lisp@conference.jabber.ru, благодаря
существованию которого данная книга и смогла появиться на свет на русском
языке.}. Существует также некоторое количество блогов, связанных с языком Lisp, большее
количество которых собрано на сайте <<Planet Lisp>> по адресу
\mbox{\url{http://planet.lisp.org}}.
И не пропускайте также во всех этих форумах анонсы собраний местных групп пользователей
языка Lisp~-- в последние несколько лет собрания лисперов возникают спонтанно во всех
городах мира, от Нью-Йорка до Окленда, от Кёльна до Мюнхена, от Женевы до Хельсинки.
Если же вы хотите продолжить изучение книг, вот вам несколько советов. В~качестве хорошего
толстого справочника можете держать на вашем столе книгу <<The ANSI Common Lisp Reference
Book>> под редакцией Дэвида Марголиса (David Margolies)(издательство Apress,
2005~г.)\pclfootnote{Другой классический справочник~-- книга Гая Стила (Guy Steele) <<Common
Lisp: The Language>> (издательство Digital Press, 1984 и 1990~г.). Первое издание,
известное также как CLtL1, было фактически стандартом языка несколько лет. Ожидая, пока
официальный стандарт ANSI будет закончен, Гай Стил, который также был в комитете ANSI по
языку Lisp, решил выпустить второе издание, чтобы восполнить разрыв между CLtL1 и
окончательным стандартом. Второе издание, теперь известное также как CLtL2~-- по сути,
слепок работы комитета по стандартизации, снятый в определённый момент времени, близкий
к концу, но всё-таки не в самом конце процесса стандартизации. Следовательно, CLtL2
отличается от стандарта в некоторых аспектах, что делает эту книгу не очень хорошим
справочником. Однако она всё же является полезным историческим документом, особенно
потому, что включает документацию по некоторым возможностям, которые были исключены из
окончательного стандарта, а также не входящие в стандарт комментарии о том, почему
что-то сделано именно так.}.
Для детального изучения объектной системы языка Lisp вы можете для начала прочитать книгу
Сони Кин (Sonya E. Keene) <<Object-Oriented Programming in Common Lisp: A Programmer's
Guide to CLOS>> (<<Объектно-ориентированное программирование в Common Lisp: Руководство
программиста по CLOS>>) (издательство Addison-Wesley, 1989~г.). Затем, если вы хотите
стать настоящим мастером, или просто чтобы расширить кругозор, прочитайте книгу авторов
Грегора Кикзалеса, Джима де Ривьереса и Даниэля Боброва (Gregor Kiczales, Jim des
Rivieres, Daniel G. Bobrow) <<The Art of the Metaobject Protocol>> (издательство MIT
Press, 1991~г.). Эта книга, также известная как AMOP, является как объяснением, что такое
метаобъектный протокол и зачем он нужен, так и фактически стандартом метаобъектного
протокола, поддерживаемого многими реализациями языка Common Lisp.
Две книги, которые охватывают общие приёмы программирования на Common Lisp,~-- это
<<Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>> Питера
Норвига (Peter Norvig) (издательство Morgan Kaufmann, 1992~г.) и <<On Lisp: Advanced
Techniques for Common Lisp>> Пола Грема (Paul Graham) (издательство Prentice Hall,
1994~г.). Первая даёт твёрдую основу приёмов искусственного интеллекта и в то же время
немного учит, как писать хороший код на Common Lisp. Вторая особенно хороша в рассмотрении
макросов.
Если вы~-- человек, который любит знать, как всё работает до последнего винтика, книга
Кристиана Квене (Christian Queinnec) <<Lisp in Small Pieces>> (издательство Cambridge
University Press, 1996~г.) сможет дать вам удачное сочетание теории языков программирования
и практических методик реализации языка Lisp. Несмотря на то что она изначально
сконцентрирована на реализации языка Scheme, а не Common Lisp, принципы, изложенные в
книге, применимы и для него.
Для людей, которые хотят более теоретического взгляда на вещи, или для тех, кто просто
хочет попробовать, каково это~-- быть новичком-студентом компьютерных наук в
Массачусетском технологическом институте, следующая книга авторов Харольда Абельсона,
Геральда Джея Сусмана и Джули Сусман (Harold Abelson, Gerald Jay Sussman, and Julie
Sussman)~-- <<Structure and Interpretation of Computer Programs, Second Edition>>
(<<Структура и интерпретация компьютерных программ>>) (издательство M.I.T. Press, 1996~г.),
классическая работа по компьютерным наукам, которая использует язык Scheme для обучения
важным концепциям программирования. Любой программист может много узнать из этой книги, не
забывайте только, что у языков Scheme и Common Lisp есть важные отличия.
Как только вы охватите своим умом язык Lisp, вам, возможно, захочется добавить чего-то
объектного. Поскольку никто не может заявлять, что он действительно понимает объекты без
знания хотя бы чего-то о языке Smalltalk, вы, возможно, захотите начать знакомство с этим
языком с книги Адели Голдберг и Дэвида Робсона (Adele Goldberg, David Robson)
<<Smalltalk-80: The Language>> (издательство Addison Wesley, 1989~г.), которая является
стандартным введением в язык Smalltalk. После этого~-- книга Кента Бека (Kent Beck)
<<Smalltalk Best Practice Patterns>> (издательство Prentice Hall, 1997~г.), которая полна
хороших советов для программистов на этом языке, большинство из которых также применимо и
к любому другому объектно-ориентированному языку.
На другом конце спектра~-- книга Бертрана Мейера (Bertrand Meyer), изобретателя часто не
замечаемого наследника Симулы и Алгола~-- языка Eiffel, <<Object-Oriented Software
Construction>> (Prentice Hall, 1997~г.), прекрасная демонстрация склада ума людей, мыслящих в
стиле статических языков программирования. Эта книга содержит много пищи для размышлений
даже для программистов, работающих с динамическими языками, каковым является язык Common
Lisp. В~частности, идеи Мейера о контрактном программировании могут помочь понять, как
нужно использовать систему условий языка Common Lisp.
Хотя и не о компьютерах как таковых, книга Джеймса Суровьеки <<Мудрость масс: почему те,
кого много, умнее тех, кого мало, и как коллективная мудрость формирует бизнес, экономику,
общество и нации>> (<<The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How
Collective Wisdom Shapes Business, Economies, Societies, and Nations>>, James Surowiecki,
Doubleday, 2004~г.) содержит великолепный ответ на вопрос: <<почему если Lisp такой
замечательный, его все не используют?>> Смотрите раздел <<Лихорадка досчатой дороги>>,
начиная со страницы~53.
И в заключениt для удовольствия и чтобы понять, какое влияние Lisp и лисперы оказали на
развитие культуры хакеров, просмотрите или прочитайте от корки до корки <<Новый словарь
хакеров>> Эрика Реймонда (<<The New Hacker's Dictionary, Third Edition>>, compiled by Eric
S. Raymond, MIT Press, 1996~г.), который базируется на исходном словаре хакеров, редактируемом
Гаем Стилом (Harper \& Row, 1983~г.).
Но не давайте всем этим книгам мешать вашему программированию, потому что единственный
путь изучить язык программирования~-- это использовать его. Если вы дошли до этого места,
вы безусловно в состоянии заняться этим.
\vspace{0.5cm}
Happy hacking!
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End:
Something went wrong with that request. Please try again.