Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

551 lines (469 sloc) 54.126 kb
\chapter{Намылить, смыть, повторить: знакомство с REPL}
\label{ch:02}
\thispagestyle{empty}
В~этой главе вы настроите среду программирования и напишете свои первые программы на
Common Lisp. Мы воспользуемся лёгким в установке дистрибутивом Lisp in a Box,
разработанным Matthew Danish и Mikel Evins, включающим в себя реализацию
Common Lisp, Emacs~-- мощный текстовый редактор, прекрасно поддерживающий Lisp, а
также SLIME\pclfootnote{Superior Lisp Interaction Mode for Emacs.}~-- среду
разработки для Common Lisp, основанную на Emacs.
Этот набор предоставляет программисту современную среду разработки для Common Lisp,
поддерживающую инкрементальный интерактивный стиль разработки, характерный для
программирования на этом языке. Среда SLIME даёт дополнительное преимущество в виде
унифицированного пользовательского интерфейса, не зависящего от выбранных вами
операционной системы и реализации Common Lisp. В~книге я буду ориентироваться на
среду Lisp in a Box, но те, кто хочет изучить другие среды разработки, например
графические интегрированные среды разработки (IDE~-- Integrated Development Environment),
предоставляемые некоторыми коммерческими поставщиками, или среды, основанные на других
текстовых редакторах, не должны испытывать больших трудностей в понимании\pclfootnote{Если у
вас уже был неудачный опыт работы с Emacs, то вам следует рассматривать Lisp in a Box как
IDE, которая использует Emacs в качестве текстового редактора. Однако для программирования
на Lisp от вас не требуется быть гуру Emacs. С другой стороны, программировать на Lisp
гораздо удобнее в редакторе, который имеет хотя бы минимальную поддержку этого языка.
Наверняка вам захочется, чтобы редактор автоматически помечал парные скобки и
сам мог расставить отступы в коде на Lisp. Так как Emacs почти целиком написан на одном
из диалектов Lisp, Elisp, он имеет очень хорошую поддержку редактирования такого
кода. История Emacs неразрывно связана с историей Lisp и культурой Lisp-хакеров: первые
версии Emacs, как и его непосредственные предшественники TECMACS и
TMACS, были написаны заинтересованными в Lisp разработчиками в Массачусетском
технологическом институте (MIT). Редакторами, использовавшимися на Lisp-машинах, были
версии Emacs, целиком написанные на Lisp. Под влиянием любви хакеров к рекурсивным
акронимам две первые реализации Emacs для Lisp-машин были названы EINE и
ZWEI, что означало <<EINE Is Not Emacs>> и <<ZWEI Was EINE Initially>>
соответственно. Некоторое время был распространён производный от ZWEI редактор,
названный более прозаично, ZMACS.}.
\section{Выбор реализации Lisp}
Первое, что вам предстоит сделать,~-- выбрать реализацию Lisp. Это может показаться
несколько странным для тех, кто раньше занимался программированием на таких языках, как Perl,
Python, Visual Basic (VB), C\# или Java. Разница между Common Lisp и этими языками
заключается в том, что Common Lisp определяется своим стандартом: не существует
единственной его реализации, контро\-ли\-руе\-мой <<великодушным диктатором>> (как в случае с Perl
и Python), или канонической реализации, контролируемой одной компанией (как в случае с VB,
C\# или Java). Любой желающий может создать свою реализацию на основе стандарта. Кроме
того, изменения в стандарт должны вноситься в соответствии с процессом, контролируемым
Американским национальным институтом стандартов (ANSI). Этот процесс организован таким
образом, что <<случайные лица>>, такие как частные поставщики программных решений, не могут
вносить изменения в стандарт по своему усмотрению\pclfootnote{На самом деле существует очень
малая вероятность пересмотра стандарта языка. Хотя есть некоторое количество
недостатков, которые пользователи языка могут желать исправить, согласно процессу
стандартизации ANSI, существующий стандарт не подлежит открытию для внесения небольших
изменений, и эти недостатки на самом деле, не вызывают ни у кого серьёзных
трудностей. Возможно, будущее стандартизации Common Lisp~-- за стандартами <<де-факто>>,
больше похожими на <<стандартизацию>> Perl и Python, когда различные разработчики
экспериментируют с интерфейсами прикладного программирования (API) и библиотеками для
реализации вещей, не описанных в стандарте языка, а другие разработчики могут принимать
их; или заинтересованные программисты будут разрабатывать переносимые библиотеки для
сглаживания различий между реализациями возможностей, не описанных в стандарте
языка.}. Таким образом, стандарт Common Lisp~-- это договор между поставщиком Common
Lisp и использующими Common Lisp разработчиками; этот договор подразумевает, что если вы
пишете программу, использующую возможности языка так, как это описано в стандарте, вы
можете рассчитывать, что эта программа запустится на любой совместимой реализации Common
Lisp.
С другой стороны, стандарт может не описывать всё то, что вам может понадобиться в ваших
программах. Более того, некоторые аспекты языка спецификация намеренно опускает, чтобы
дать возможность экспериментальным путём решить спорные вопросы языка. Таким образом,
каждая реализация предоставляет пользователям как возможности, входящие в стандарт, так и
выходящие за его пределы. В~зависимости от того, что вы хотите программировать, вы можете
выбрать реализацию Common Lisp, поддерживающую именно те дополнительные возможности,
которые вам больше всего понадобятся. С другой стороны, если вы пишете код, которым будут
пользоваться другие разработчики, то вы, вероятно, захотите~-- конечно, в пределах
возможного~-- писать переносимый код на Common Lisp. Для нужд написания переносимого кода,
который в то же время использует возможности, не описанные в стандарте, Common Lisp
предоставляет гибкий способ писать код, <<зависящий>> от возможностей текущей
реализации. Вы увидите пример такого кода в главе~\ref{ch:15}, когда мы будем
разрабатывать простую библиотеку, <<сглаживающую>> некоторые различия в обработке имён
файлов разными реализациями Lisp.
Сейчас, однако, наиболее важная характеристика реализации~-- её способность работать в
вашей любимой операционной системе. Сотрудники компании Franz, занимающейся разработкой
Allegro Common Lisp, выпустили пробную версию своего продукта, работающего на GNU/Linux,
Windows и OS X, предназначенную для использования с этой книгой. У читателей,
предпочитающих реализации с открытым исходным кодом, есть несколько
вариантов. SBCL\pclfootnote{Steel Bank Common Lisp.}~-- высококачественная открытая
реализация, способная компилировать в машинный код и работать на множестве различных
UNIX-систем, включая Linux и OS X. SBCL~-- <<наследник>> CMUCL\pclfootnote{CMU Common
Lisp.}~-- реализации Common Lisp, разработанной в университете Carnegie Mellon,
и, как и CMUCL, является всеобщим достоянием (public domain), за исключением нескольких
частей, покрываемых BSD-подобными (Berkley Software Distributions) лицензиями. CMUCL~--
тоже хороший выбор, однако SBCL обычно легче в установке и поддерживает 21-разрядный
Unicode\pclfootnote{SBCL стал <<ответвлением>> CMUCL, так как его разработчики хотели
сосредоточиться на упорядочивании его внутренней организации и сделать его легче в
сопровождении. <<Ответвление>> вышло очень удачным. Исправления ошибок привели к появлению
серьёзных различий между двумя проектами, и, как теперь поговаривают, их снова планируют
объединить.}. OpenMCL будет отличным выбором для пользователей OS X: эта
реализация способна компилировать в машинный код, поддерживать работу с потоками, а также
прекрасно интегрируется с инструментальными комплектами Carbon и Cocoa. Кроме
перечисленных, существуют и другие свободные и коммерческие реализации. В~главе~\ref{ch:32}
перечислены источники для получения дополнительной информации.
Весь код на Lisp, приведённый в этой книге, должен работать на любой совместимой
реализации Common Lisp, если явно не указано обратное, и SLIME будет <<сглаживать>>
некоторые различия между реализациями, предоставляя общий интерфейс для взаимодействия с
Lisp. Сообщения интерпретатора, приведённые в этой книге, сгенерированы Allegro,
запущенным на GNU/Linux. В~некоторых случаях другие реализации Lisp могут
генерировать сообщения, незначительно отличающиеся от приведённых.
\section{Введение в Lisp in a Box}
Поскольку Lisp in a Box спроектирован с целью быть <<дружелюбным>> к новичкам, а
также предоставлять первоклассную среду разработки на Lisp, всё, что вам нужно для
работы,~-- выбрать пакет, соответствующий вашей операционной системе, с веб-сайта
Lisp in~a Box (см.~главу~\ref{ch:32}), а затем просто следовать инструкциям
по установке.
Так как Lisp in a Box использует Emacs в качестве текстового редактора, вам стоит хоть
немного научиться им пользоваться. Возможно, лучший способ начать работать с Emacs~-- это
изучать его по встроенному учебнику (tutorial). Чтобы вызвать учебник, выберите первый
пункт меню Help~-- Emacs tutorial. Или же зажмите \texttt{Ctrl} и нажмите~\texttt{h}, затем отпустите \texttt{Ctrl} и
нажмите~\texttt{t}. Большинство команд в Emacs доступно через комбинации клавиш, поэтому они будут
встречаться довольно часто, и чтобы долго не описывать комбинации (например: <<зажмите \texttt{Ctrl}
и нажмите \texttt{h}, затем...>>), в Emacs существует краткая форма записи комбинаций
клавиш. Клавиши, которые должны быть нажаты вместе, пишутся вместе, разделяются тире и
называются связками. Связки разделяются пробелами. \texttt{C}~обозначает \texttt{Ctrl}, а \texttt{M}~-- \texttt{Meta}
(\texttt{Alt}). Например, вызов tutorial будет выглядеть следующим образом: \texttt{C-h t}.
Учебник также описывает много других полезных команд Emacs и вызывающих их комбинаций
клавиш. Более того, у Emacs есть расширенная онлайн-документация, для просмотра которой
используется специальный браузер~-- Info. Чтобы её вызвать, нажмите \texttt{C-h i}. У Info,
в свою очередь, есть своя справка, которую можно вызвать, нажав клавишу h, находясь в браузере Info. Emacs
предоставляет ещё несколько способов получить справку~-- это все сочетания клавиш,
начинающиеся с~\texttt{C-h},~-- полный список по~\texttt{C-h ?}. В~этом списке есть две
полезные вещи: \texttt{C-h k} <<объяснит>> комбинацию клавиш, а~\texttt{C-h w}~-- команду.
Ещё одна важная часть терминологии (для тех, кто отказался от работы с учебником)~-- это
\emph{буфер}. В~Emacs каждый файл, который вы редактируете, представлен в
отдельном буфере. Только один буфер может быть <<текущим>> в любой момент времени. В~текущий
буфер поступает весь ввод~-- всё, что вы печатаете, и любые команды, которые
вызываете. Буферы также используются для представления взаимодействия с программами
(например, с Common Lisp). Есть одна простая вещь, которую вы должны знать,~-- <<переключение
буферов>>, означающее смену текущего буфера, так что вы можете редактировать определённый
файл или взаимодействовать с определённой программой. Команда \texttt{switch-to-buffer},
привязанная к комбинации клавиш \texttt{C-x b}, запрашивает имя буфера (в нижней части окна
Emacs). Во время ввода имени буфера вы можете пользоваться автодополнением по клавише
Tab, которое по начальным символам завершает имя буфера или выводит список возможных
вариантов. Просто нажав Enter, вы переключитесь в буфер <<по~умолчанию>> (таким же образом и
обратно). Вы также можете переключать буферы, выбирая нужный пункт в меню Buffers.
В~определённых контекстах для переключения на определённые буферы могут быть доступны
другие комбинации клавиш. Например, при редактировании исходных файлов Lisp сочетание
клавиш \texttt{C-c C-z} переключает на буфер, в котором вы вза\-и\-мо\-дей\-ству\-е\-те с Lisp.
\section{Освободите свой разум: интерактивное программирование}
При запуске Lisp in a Box вы должны увидеть примерно следующее приглашение:
\begin{myverb}
CL-USER>
\end{myverb}
Это приглашение Lisp. Как и приглашение оболочки DOS или UNIX, приглашение Lisp~-- это
место, куда вы можете вводить выражения, которые заставляют компьютер что-либо делать.
Однако, вместо того чтобы считывать и выполнять строку команд оболочки, Lisp
считывает Lisp-выражения, вычисляет их согласно правилам Lisp и печатает результат. Потом
он повторяет свои действия со следующим введённым вами выражением. Такой бесконечный цикл
считывания, вычисления и печати (вывода на экран) называется
\emph{цикл-чтение-вычисление-печать} (по-английски \emph{read-eval-print-loop}), или
сокращённо REPL. Этот процесс может также называться \emph{top-level},
\emph{top-level listener}, или \emph{Lisp listener}.
Через окружение, предоставленное REPL, вы можете определять и переопределять элементы
программ, такие как переменные, функции, классы и методы; вычислять выражения Lisp;
загружать файлы, содержащие исходные тексты Lisp или скомпилированные программы;
компилировать целые файлы или отдельные функции; входить в отладчик; пошагово выполнять
программы; проверять состояние отдельных объектов Lisp.
Все эти возможности встроены в язык и доступны через функции, определённые в стандарте
языка. Если вы захотите, то можете построить достаточно приемлемую среду разработки только
из REPL и текстового редактора, который знает, как правильно форматировать код Lisp. Но для
истинного опыта Lisp-программирования вам необходима среда разработки типа SLIME, которая
бы позволяла вам взаимодействовать с Lisp как посредством REPL, так и при редактировании
исходных файлов. Например, вы ведь не хотите каждый раз копировать и вставлять куски
кода из редактора в REPL или перезагружать весь файл только потому, что изменилось одно
определение, ваше окружение должно позволять вам вычислять или компилировать как отдельные
выражения, так и целые файлы из вашего редактора\translationnote{Для использования русского языка
необходима соотв. настройка Emacs, Slime(cvs-версия), и вашего интерпретатора Lisp.}.
\section{Эксперименты в REPL}
Для знакомства с REPL вам необходимо выражение Lisp, которое может быть прочитано,
вычислено и выведено на экран. Простейшее выражение Lisp~-- это число. Если вы наберёте
\lstinline{10} в приглашении Lisp и нажмёте Enter, то увидите что-то наподобие:
\begin{myverb}
CL-USER> 10
10
\end{myverb}
Первая \lstinline{10}~-- это то, что вы набрали. \emph{Считыватель} Lisp, \emph{R} в REPL,
считывает текст <<10>> и создаёт объект Lisp, представляющий число 10. Этот объект~--
\emph{самовычисляемый} объект, это означает, что такой объект при передаче в \emph{вычислитель},
\emph{E} в REPL, вычисляется сам в себя. Это значение подаётся на устройство вывода REPL,
которое напечатает объект \lstinline{10} в отдельной строке. Хотя это и похоже на сизифов труд, можно
получить что-то поинтереснее, если дать интерпретатору Lisp пищу для размышлений.
Например, вы можете набрать \lstinline{(+ 2 3)} в приглашении Lisp.
\begin{myverb}
CL-USER> (+ 2 3)
5
\end{myverb}
Все, что в скобках,~-- это список, в данном случае список из трёх элементов: символ \lstinline{+}
и числа~2 и~3. Lisp, в общем случае, вычисляет списки, считая первый элемент именем
функции, а остальные~-- выражениями для вычисления и передачи в качестве аргументов этой
функции. В~нашем случае символ \lstinline{+}~-- название функции, которая вычисляет сумму. 2 и 3
вычисляются сами в себя и передаются в функцию суммирования, которая возвращает
5. Значение 5 отправляется на устройство вывода, которое отображает его. Lisp может
вычислять выражения и другими способами, но нам пока не нужно вдаваться в такие детали.
В~первую очередь нам нужно написать\ldots
\section{<<Hello, world>> в стиле Lisp}
Нет законченной книги по программированию без программы <<hello,
world>>\pclfootnote{Досточтимая фраза <<hello, world>> предшествует даже классической книге по
языку C Кернигана и Ритчи, которая сыграла огромную роль в её
популяризации. Первоначальный <<hello, world>>, похоже, пришёл из книги Брайана Кернигана <<A
Tutorial Introduction to the Language B>>, которая была частью \emph{Bell Laboratories
Computing Science Technical Report \#8: The Programming Language B}, опубликованного в
январе 1973 г. (Отчёт выложен в Интернете
\url{http://cm.bell-labs.com/cm/cs/who/dmr/bintro.html}.) }. После того как
интерпретатор запущен, нет ничего проще, чем набрать строку <<hello, world>>.
\begin{myverb}
CL-USER> "hello, world"
"hello, world"
\end{myverb}
Это работает, поскольку строки, так же как и числа, имеют символьный синтаксис, понимаемый
процедурой чтения Lisp, и являются самовычисляемыми объектами: Lisp считывает строку в
двойных кавычках и создаёт в памяти строковой объект, который при вычислении вычисляется
сам в себя и потом печатается в том же символьном представлении. Кавычки не являются
частью строкового объекта в памяти~-- это прос\-то синтаксис, который позволяет процедуре чтения
определить, что этот объект~-- строка. Устройство вывода REPL напечатает кавычки тоже,
потому что оно пытается выводить объекты в таком же виде, в каком понимает их процедура чтения.
Однако наш пример не может квалифицироваться как \emph{программа} <<hello, world>>.
Это, скорее, \emph{значение} <<hello, world>>.
Вы можете сделать шаг к настоящей программе, напечатав код, который в качестве побочного
эффекта отправит на стандартный вывод строку <<hello, world>>. Common Lisp предоставляет
несколько путей для вывода данных, но самый гибкий~-- это функция \lstinline{FORMAT}. \lstinline{FORMAT} получает
переменное количество параметров, но только два из них обязательны: указание, куда
осуществлять вывод, и строка для вывода. В~следующей главе вы увидите, как строка может
содержать встроенные директивы, которые позволяют вставлять в строку последующие параметры
функции (а-ля \lstinline{printf} или строка \lstinline|%| из Python). До тех пор, пока строка
не содержит символа \lstinline|~|, она будет выводиться как есть. Если вы передадите \texttt{t} в
качестве первого параметра, функция \lstinline{FORMAT} направит отформатированную строку на
стандартный вывод. Итак, выражение \lstinline{FORMAT} для печати <<hello, world>> выглядит примерно
так\pclfootnote{Есть несколько других выражений, которые тоже выводят строку <<hello,
world>>:
\lstinline{(write-line "hello, world")}
\noindent{}или
\lstinline{(print "hello, world")}}:
\begin{myverb}
CL-USER> (format t "hello, world")
hello, world
NIL
\end{myverb}
Стоит отметить, что результатом выражения \lstinline{FORMAT} является \lstinline{NIL} в строке после
вывода <<hello, world>>. Этот \lstinline{NIL} является результатом вычисления выражения \lstinline{FORMAT},
напечатанного REPL. (\lstinline{NIL}~-- это Lisp-версия false и/или null. Подробнее об этом
рассказывается в главе~\ref{ch:04}.) В~отличие от других выражений, рассмотренных ранее, нас больше
интересует побочный эффект выражения \lstinline{FORMAT} (в данном случае печать на стандартный
вывод), чем возвращаемое им значение. Но каждое выражение в Lisp вычисляется в некоторый
результат\pclfootnote{На самом деле, как вы увидите, когда будет рассказано о возврате
множественных значений, технически возможно написание выражений, которые не вычисляются
ни в какие значения, но даже такие выражения рассматриваются как возвращающие \lstinline{NIL} при
вычислении в контексте, ожидающем возврата значения.}.
Однако до сих пор остаётся спорным, написали ли мы настоящую программу. Но мы близки к этому.
Вы видите восходящий стиль программирования, поддерживаемый REPL: вы можете
экспериментировать с различными подходами и строить решения из уже протестированных
частей. Теперь, когда у вас есть простое выражение, которое делает то, что вы хотите,
нужно просто упаковать его в функцию. Функции являются одним из основных строительных
материалов в Lisp и могут быть определены с помощью выражения \lstinline{DEFUN} подобным образом:
\begin{myverb}
CL-USER> (defun hello-world () (format t "hello, world"))
HELLO-WORLD
\end{myverb}
Выражение \lstinline{hello-world}, следующее за \lstinline{DEFUN}, является именем функции. В~главе~\ref{ch:04} мы
рассмотрим, какие именно символы могут использоваться в именах, но сейчас будет достаточно
сказать, что многие символы, такие как~\lstinline|-|, недопустимые в именах в других
языках, можно использовать в Common Lisp. Это стандартный стиль Lisp~-- не говоря уже о том,
что это больше соответствует нормальному английскому написанию,~-- формирование составных имён с помощью дефисов,
как в \lstinline{hello-world}, вместо использования знаков подчёркивания, как в \lstinline|hello_world|, или
использования заглавных букв внутри имени, как \lstinline{helloWorld}. Скобки \lstinline{()} после имени отделяют
список параметров, который в данном случае пуст, так как функция не принимает
аргументов. Остальное~-- это тело функции.
В~какой-то мере это выражение подобно всем другим, которые вы видели, всего лишь ещё одно
выражение для чтения, вычисления и печати, осуществляемых REPL. Возвращаемое значение в
этом случае~-- это имя только что определённой функции\pclfootnote{В~главе~\ref{ch:04} будет
рассказано, почему имя преобразуется в верхний регистр.}. Но, подобно выражению
\lstinline{FORMAT}, это выражение более интересно своими побочными эффектами, нежели
возвращаемым значением. Однако, в отличие от выражения \lstinline{FORMAT}, побочные
эффекты невидимы: после вычисления этого выражения создаётся новая функция, не принимающая
аргументов, с телом \lstinline{(format t "hello, world")}, и ей даётся имя
\lstinline{HELLO-WORLD}.
Теперь, после определения функции, вы можете вызвать её следующим образом:
\begin{myverb}
CL-USER> (hello-world)
hello, world
NIL
\end{myverb}
Видно, что вывод в точности такой же, как при вычислении выражения \lstinline{FORMAT}
напрямую, включая значение \lstinline{NIL}, напечатанное REPL. Функции в Common Lisp автоматически
возвращают значение последнего вычисленного выражения.
\section{Сохранение вашей работы}
Вы могли бы утверждать, что это готовая программа <<hello, world>>. Однако остаётся одна
проблема. Если вы выйдете из Lisp и перезапустите его, определение функции
исчезнет. Написав такую изящную функцию, вы захотите сохранить вашу работу.
Это достаточно легко. Вы просто должны создать файл, в котором сохраните определение. В
Emacs вы можете создать новый файл, набрав \texttt{C-x C-f}, и затем, когда Emacs выведет подсказку,
введите имя файла, который вы хотите создать. Не важно, где именно будет находиться этот
файл. Обычно исходные файлы Common Lisp именуются с расширением \texttt{.lisp}, хотя некоторые люди
предпочитают \texttt{.cl}.
Открыв файл, вы можете набрать определение функции, введённое ранее в области
REPL. Обратите внимание, что после набора открывающей скобки и слова \lstinline{DEFUN} в нижней части
окна Emacs SLIME подскажет вам предполагаемые аргументы. Точная форма зависит от
используемой вами реализации Common Lisp, но, вероятно, вы увидите что-то вроде этого:
\begin{myverb}
(defun name varlist &rest body)
\end{myverb}
Сообщение будет исчезать, когда вы будете начинать печатать каждый новый элемент, и снова
появляться после ввода пробела. При вводе определения в файл вы можете захотеть разбить
определение после списка параметров так, чтобы оно занимало две строки. Если вы нажмёте
Enter, а затем Tab, SLIME автоматически выровняет вторую строку соответствующим
образом\pclfootnote{Вы также можете ввести определение в двух строках в REPL, так как REPL
читает выражение целиком, а не по строкам.}:
\begin{myverb}
(defun hello-world ()
(format t "hello, world"))
\end{myverb}
SLIME также поможет вам и в согласовании скобок~-- как только вы наберёте закрывающую
скобку, SLIME подсветит соответствующую открывающую скобку. Или вы можете просто набрать
\texttt{C-c C-q} для вызова команды \texttt{slime-close-parens-at-point}, которая вставит столько
закрывающих скобок, сколько нужно для согласования со всеми открытыми скобками.
Теперь вы можете отправить это определение в вашу среду Lisp несколькими способами. Самый
простой~-- это набрать \texttt{C-c C-c}, когда курсор находится где-нибудь внутри или сразу после
формы \lstinline{DEFUN}, что вызовет команду \texttt{slime-compile-defun}, которая, в свою очередь, пошлёт
определение в Lisp для вычисления и компиляции. Для того чтобы убедиться, что это
работает, вы можете сделать несколько изменений в \lstinline{hello-world}, перекомпилировать её, а
затем вернуться назад в REPL, используя \texttt{C-c C-z} или \texttt{C-x b}, и вызвать её снова. Например,
вы можете сделать эту функцию более грамматически правильной.
\begin{myverb}
(defun hello-world ()
(format t "Hello, world!"))
\end{myverb}
Затем перекомпилируем её с помощью \texttt{C-c C-c} и перейдём в REPL, набрав \texttt{C-c C-z}, чтобы
попробовать новую версию.
\begin{myverb}
CL-USER> (hello-world)
Hello, world!
NIL
\end{myverb}
Вы, возможно, захотите сохранить файл, с которым работаете; находясь в буфере
\texttt{hello.lisp}, наберите \texttt{C-x C-s} для вызова функции Emacs \texttt{save-buffer}.
Теперь, для того чтобы попробовать перезагрузить эту функцию из файла с исходным кодом,
вы должны выйти из Lisp и перезапустить его. Для выхода вы можете использовать клавишную
комбинацию SLIME: находясь в REPL, наберите запятую. Внизу окна Emacs вам будет предложено
ввести команду. Наберите \texttt{quit} (или \texttt{sayoonara}), а затем нажмите Enter. Произойдёт выход из
Lisp, а все окна, созданные SLIME (такие как буфер REPL), закроются\pclfootnote{клавишные
комбинации SLIME~-- это не часть Common Lisp, это команды SLIME.}. Теперь перезапустите
SLIME, набрав \texttt{M-x slime}.
Просто ради интереса вы можете попробовать вызвать \lstinline{hello-world}.
\begin{myverb}
CL-USER> (hello-world)
\end{myverb}
После этого возникнет новый буфер SLIME, содержимое которого будет начинаться с чего-то
вроде этого:
\begin{myverb}
attempt to call `HELLO-WORLD' which is an undefined function.
[Condition of type UNDEFINED-FUNCTION]
Restarts:
0: [TRY-AGAIN] Try calling HELLO-WORLD again.
1: [RETURN-VALUE] Return a value instead of calling HELLO-WORLD.
2: [USE-VALUE] Try calling a function other than HELLO-WORLD.
3: [STORE-VALUE] Setf the symbol-function of HELLO-WORLD and call it again.
4: [ABORT] Abort handling SLIME request.
5: [ABORT] Abort entirely from this process.
Backtrace:
0: (SWANK::DEBUG-IN-EMACS #<UNDEFINED-FUNCTION @ #x716b082a>)
1: (FLET SWANK:SWANK-DEBUGGER-HOOK SWANK::DEBUG-IT)
2: (SWANK:SWANK-DEBUGGER-HOOK #<UNDEFINED-FUNCTION @ #x716b082a>
#<Function SWANK-DEBUGGER-HOOK>)
3: (ERROR #<UNDEFINED-FUNCTION @ #x716b082a>)
4: (EVAL (HELLO-WORLD))
5: (SWANK::EVAL-REGION "(hello-world)
" T)
\end{myverb}
Что же произошло? Просто вы попытались вызвать функцию, которая не сущес\-твует. Но несмотря
на такое количество выведенной информации, Lisp на самом деле обрабатывает подобную ситуацию
изящно. В~отличие от Java или Python, Common Lisp не просто генерирует исключение и
разворачивает стек. И он точно не завершается, оставив после себя образ памяти (core
dump), только потому, что вы попытались вызвать несуществующую функцию. Вместо этого он
перенесёт вас в отладчик.
Во время работы с отладчиком вы все ещё имеете полный доступ к Lisp, поэтому вы можете
вычислять выражения для исследования состояния вашей программы и, может быть, даже для
исправления каких-то вещей. Сейчас не стоит беспокоиться об этом; просто наберите \texttt{q} для
выхода из отладчика и возвращения назад в REPL. Буфер отладчика исчезнет, а REPL выведет
следующее:
\begin{myverb}
CL-USER> (hello-world)
; Evaluation aborted
CL-USER>
\end{myverb}
Конечно, в отладчике можно сделать гораздо больше, чем просто выйти из него,~-- в
главе~\ref{ch:19} мы увидим, например, как отладчик интегрируется с системой обработки
ошибок. А сейчас, однако, важной вещью, которую нужно знать, является то, что вы всегда
можете выйти из отладчика и вернуться обратно в REPL, набрав \texttt{q}.
Вернувшись в REPL, вы можете попробовать снова. Ошибка произошла, потому что Lisp не знает
определения \lstinline{hello-world}. Поэтому вам нужно предоставить Lisp определение, сохранённое нами
в файле \texttt{hello.lisp}. Вы можете сделать это несколькими способами. Вы можете переключиться
назад в буфер, содержащий файл (наберите \texttt{C-x b}, а затем введите \texttt{hello.lisp}), и
перекомпилировать определение, как вы это делали ранее с помощью \texttt{C-c C-c}. Или вы можете
загрузить файл целиком (что будет более удобным способом, если файл содержит множество
определений) путём использования функции \lstinline{LOAD} в REPL следующим образом:
\begin{myverb}
CL-USER> (load "hello.lisp")
; Loading /home/peter/my-lisp-programs/hello.lisp
T
\end{myverb}
\lstinline{T} означает, что загрузка всех определений произошла успешно\pclfootnote{Если по каким-то
причинам \lstinline{LOAD} не выполнилась успешно, вы получите другую ошибку и будете перенаправлены
в отладчик. Если это произошло, наиболее вероятной причиной может быть то, что Lisp не
может найти файл, возможно из-за того, что его текущая директория не совпадает с той, в
которой находится файл. В~этом случае вы можете выйти из отладчика, набрав \texttt{q}, а затем
использовать клавиатурную комбинацию SLIME \texttt{cd} для изменения текущей директории~--
наберите запятую, а затем на приглашение к вводу команды~-- \texttt{cd} и имя директории, где был
сохранён \texttt{hello.lisp}.}. Загрузка файла с помощью \lstinline{LOAD} в сущности эквивалентна набору
каждого выражения этого файла в REPL в том порядке, в каком они находятся в файле. Таким
образом, после вызова \lstinline{LOAD} \lstinline{hello-world} должен быть определён.
\begin{myverb}
CL-USER> (hello-world)
Hello, world!
NIL
\end{myverb}
Ещё один способ загрузки определений из файла~-- предварительная компиляция файла с помощью
\lstinline{COMPILE-FILE}, а затем загрузка (с помощью \lstinline{LOAD}) уже скомпилированного файла, называемого
\emph{FASL-файлом}, что является сокращением для \emph{fast-load file} (быстро загружаемый
файл). \lstinline{COMPILE-FILE} возвращает имя FASL-файла, таким образом мы можем скомпилировать и
загрузить файл из REPL следующим образом:
\begin{myverb}
CL-USER> (load (compile-file "hello.lisp"))
;;; Compiling file hello.lisp
;;; Writing fasl file hello.fasl
;;; Fasl write complete
; Fast loading /home/peter/my-lisp-programs/hello.fasl
T
\end{myverb}
SLIME также предоставляет возможность загрузки и компиляции файлов без использования
REPL. Когда вы находитесь в буфере с исходным кодом, вы можете использовать \texttt{C-c C-l} для
загрузки файла с помощью \texttt{slime-load-file}. Emacs выведет запрос имени файла для загрузки с
уже введённым именем текущего файла; вы можете просто нажать Enter. Или же вы можете
набрать \texttt{C-c C-k} для компиляции и загрузки файла, представляемого текущим буфером. В
некоторых реализациях Common Lisp компилирование кода таким образом выполнится немного
быстрее; в других~-- нет, потому что они обычно компилируют весь файл целиком.
Этого должно быть достаточно, чтобы дать вам почувствовать красоту того, как
осуществляется программирование на Lisp. Конечно, я пока не описал всех трюков и техник,
но вы увидели важнейшие элементы~-- взаимодействие с REPL, загрузку и тестирование нового
кода, настройку и отладку. Серьёзные Lisp-хакеры часто держат интерпретатор непрерывно
запущенным многие дни, добавляя, переопределяя и тестируя части своих программ
инкрементально.
Кроме того, даже если написанное на Lisp приложение уже развёрнуто, нередко существует
возможность обратиться к REPL. В~главе~\ref{ch:26} вы увидите, как можно использовать REPL
и SLIME для взаимодействия с Lisp, запустившим Web-сервер, в то же самое время, когда он
продолжает отдавать веб-страницы. SLIME можно использовать даже для соединения с Lisp,
запущенным на другой машине, что позволяет, например, отлаживать удалённый сервер так же,
как локальный.
И даже более впечатляющий пример удалённой отладки произошёл в миссии NASA <<Deep Space 1>>
в 1998 году. Через полгода после запуска космического корабля небольшой код на Lisp
должен был управлять космическим кораблём в течение двух дней для проведения серии
экспериментов. Однако неуловимое состояние гонки (race condition) в коде не было выявлено
при тестировании на земле и было обнаружено уже в космосе~-- в 100 миллионах миль от Земли.
Команда смогла произвести диагностику и исправление работающего кода, что позволило завершить
эксперимент\pclfootnote{\url{http://www.flownet.com/gat/jpl-lisp.html}.}. Один из
программистов сказал об этом следующее:
\emph{Отладка программы, работающей на оборудовании стоимостью 100 миллионов долларов, которая
находится в 100 миллионах миль от вас, является интересным опытом. REPL, работающий на
космическом корабле, предоставляет бесценные возможности в нахождении и устранении
проблем.}
Вы пока не готовы отправлять какой бы то ни было код Lisp в дальний космос, но в следующей
главе вы напишете программу, которая немного более интересна, чем <<hello, world>>.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End:
Jump to Line
Something went wrong with that request. Please try again.