[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1. Обработка списков

С первого взгляда Лисп кажется очень необычным языком программирования. В программах на Лисп очень много круглых скобок. Некоторые люди даже заявляют, что сокращение Lisp происходит от "Lots of Isolated Silly Parentheses" (2). Но это утверждение несправедливо. LISP означает LISt Processing (3) и этот язык программирования обрабатывает списки (и списки списков), располагая их между круглых скобок. Круглые скобки ограничивают список. Иногда перед списком ставится один апостроф `''. Списки --- основа Лиспа.

1.1 Списки в Лисп  
1.2 Запуск Программы  
1.3 Получаем сообщение об ошибке  
1.4 Имена символов и определения функций  
1.5 Интерпретатор Лиспа  
1.6 Вычисление  
1.7 Переменные  
1.8 Аргументы  
1.9 Присваиваем переменной значение  
1.10 Резюме  
1.11 Упражнения  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1 Списки в Лисп

В Лиспе список выглядит следующим образом: '(роза фиалка маргаритка лютик). Перед этим списком поставлен один апостроф. Этот список можно переписать по другому, именно так в дальнейшем и будут выглядеть списки с которыми вам придется иметь дело:

 
'(роза
фиалка
маргаритка
лютик)

Элементами этого списка являются названия четырех разных цветов, разделенные друг от друга пробелами и окруженные круглыми скобками, подобно цветам в саду с каменной оградой.

Списки могут также содержать числа, например: (+ 2 2). В этом списке первый элемент это знак плюс +, за которым следуют два числа, разделенные пробелами.

В Лиспе и данные, и программы представляются одинаковым образом, то есть в виде списков, состоящих из слов, чисел или других списков, разделенные пробельными символами и окруженные круглыми скобками. (Так как программы выглядят как данные, то одна программа легко может служить данными для другой программы --- это очень мощное свойство Лиспа). (Кстати, эти два замечания в скобках не являются допустимыми списками Лиспа, поскольку они содержат знаки препинания `;' и `.').

Вот другой список, в этот раз со списком внутри себя:

 
'(этот список содержит список (внутри себя))

Элементами этого списка являются слова `этот', `список', `содержит' и список `(внутри себя)'. Внутренний список составлен из слов `внутри', `себя'.

1.1.1 Атомы Лиспа  
1.1.2 Пробелы в списках  
1.1.3 GNU Emacs помогает вам программировать  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.1 Атомы Лиспа

То, что мы называем словами, в Лиспе называют атомами. Этот термин происходит от исторического значения слова атом, что значит "неделимый". Что касается Лиспа, то слова, которые мы использовали в списках, нельзя разделить на меньшие части; то же самое можно сказать о числах и одиночных символах, таких как `+'. С другой стороны, в отличие от атома список можно разложить на составные части. (car cdr & cons: основные функции.)

В списке атомы разделяются друг от друга пробелами и могут находиться вплотную к скобкам.

Если рассматривать это с технической точки зрения, список в Лиспе состоит из круглых скобок, окружающих атомы, другие списки, или и списки, и атомы, разделенные пробелами. Список может иметь только один атом внутри себя или не иметь совсем ничего. Список, который ничего не содержит, выглядит следующим образом: () и называется пустым списком. В отличие от всего остального, пустой список считается и атомом, и списком одновременно.

Печатное представление и атомов, и списков называют символическим выражением, или говоря точнее s-выражениями. Слово выражение может относиться печатному представлению, к атому или списку, как они содержатся внутри компьютера. Люди часто используют термин выражение не по назначению. (Также во многих книгах по Лиспу, слово форма используется, как синоним слова выражение).

Кстати, атомы, из которых состоит наша вселенная, были названы так, когда их считали неделимыми; но позже выяснилось, что это не совсем правильно. Атом можно расщепить на две части примерно одинакового размера, или он может распасться сам. Следовательно, физические атомы были названы преждевременно, до того, как прояснилась их истинная природа. В Лиспе некоторые виды атомов, такие как массивы, можно разделить на части, но механизм разделения отличается от механизма разделения списка на составные части. Когда дело касается операций на списках, атомы списка неразделимы.

Как и в английском языке, значения букв, составляющих название атома Лиспа, отличается от значения букв, составляющих слово. Например, южно-американский ленивец по английски называется `ai', и это полностью отличается от двух букв `a' и `i'.

В природе существует много различных видов атомов, но в Лиспе существует только несколько: например, числа, такие как 37, 511, или 1729; или символы,такие как `+', `foo', `forward-line'. Слова, которые мы перечислили выше, являются символами. В обычных разговорах программистов слово "атом" используется не так часто, потому что программисты обычно пытаются быть более конкретны при указании типов атомов, которые они используют. Программирование на Лиспе в основном имеет дело с символами (и иногда с числами) внутри списков. (Кстати, предыдущие четыре слова в скобках являются правильным списком для Лиспа, так как он состоит из атомов, которые в этом случае являются символами, разделенными пробелами и закрытые в скобки, без каких либо знаков препинания).

Добавим, что текст между двойными кавычками (даже предложения или параграфы) считается одним атомом. Например:

 
'(этот список включает "текст между двойными кавычками.")

В Лиспе весь текст между двойными кавычками, включая знаки препинания и пробельные символы, считается атомом. Этот вид атома называется строка (от "строка символов") и обычно используется для сообщений, которые компьютер печатает для того, чтобы пользователь мог их прочесть. Строки отличаются от чисел и символов и используются по-другому.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.2 Пробелы в списках

Количество пробелов в списке не имеет значения. С точки зрения синтаксиса Лиспа,

 
'(это одинаковые
   списки)

это точно такой же список как и:

 
'(это одинаковые списки)

Для Лиспа оба этих списка абсолютно одинаковы и состоят из символов `это', `одинаковые', `списки' именно в таком порядке.

Дополнительные пробелы и символы новой строки используются для того, чтобы сделать список более удобным для чтения людьми. Когда Лисп считывает выражение, он избавляется от лишних пробелов (но для того, чтобы атомы можно было воспринимать раздельно, между ними должен быть по меньшей мере один пробел.)

Это может показаться удивительным, но мы рассмотрели почти все виды списков Лиспа! Любой другой список в Лиспе выглядит более или менее похожим на один из выше рассмотренных примеров, разве что список может быть больше и выглядеть более сложным. Короче говоря, список находится между круглых скобок, строка находится между двойными кавычками, символ выглядит как слово, а число выглядит как число. (В определенных случаях могут использоваться квадратные скобки, точки и некоторые другие специальные символы; однако мы можем зайти довольно далеко и без них).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.3 GNU Emacs помогает вам программировать

Если вы набираете Лисп-выражение в GNU Emacs, используя основной режим Emacs Lisp или Lisp Interaction, то вы можете воспользоваться некоторыми командами, чтобы отформатировать Лисп-выражение так, чтобы его было легче воспринимать при чтении. Например, нажатие клавиши TAB автоматически выравнивает строку, в которой находится курсор, вправо. Команда для выравнивания области текста обычно привязана к сочетанию клавиш M-C-\. Выравнивание производится так, чтобы вы могли сразу увидеть, к какому списку принадлежат данные выражения списка (элементы вложенных списков расположены правее, чем элементы списков более верхнего уровня).

Кроме этого, когда вы печатаете закрывающую скобку, Emacs моментально переводит курсор к соответствующей ей открывающей скобке. Это очень полезная функция редактора, так как в каждом списке, который вы будете набирать, все скобки должны быть сбалансированы. (See section `Major Modes' in The GNU Emacs Manual, для получения дополнительной информации о режимах Emacs).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.2 Запуск Программы

Список в Лиспе (любой список) --- это программа, готовая к запуску. Если вы запустите ее (на жаргоне Лиспа вычислите), то компьютер сделает следующее: 1) ничего не сделает, а только вернет вам сам список; 2) отобразит сообщение об ошибке; 3) обработает первый символ в списке как команду сделать что-либо. (Обычно, именно последнее --- это то, что вы хотите на самом деле!)

Одиночный апостроф ', который я ставил перед некоторыми из списков в предыдущих абзацах, называют цитатой (quote) и, когда перед списком стоит апостроф, это сигнал для Лиспа ничего не делать со списком, а взять его как есть. Но если перед списком нет апостофа, то первый символ в списке имеет особое значение: --- это команда, которую компьютер должен выполнить. (В Лиспе эти команды называют функциями). Перед списком (+ 2 2) нет апострофа, поэтому Лисп будет рассматривать + как инструкцию, сделать что-то с остальными символами списка; в этом примере он просто сложит последующие числа.

Если вы читаете это в Info внутри Gnu Emacs, то вот как вы можете вычислить такой список: расположите курсор сразу после правой скобки следующего ниже списка и нажмите сочетание клавиш C-x C-e:

 
(+ 2 2)

Вы увидите, что в эхо-области появится число 4. (Говоря на жаргоне Лиспа, вы только что "вычислили список". Эхо-область--- это строка внизу экрана, в которой отображается текст). Сейчас попробуйте сделать то же самое со списком, перед которым стоит апостроф: поместите курсор сразу за следующим списком и нажмите C-x C-e:

 
'(это список перед которым стоит апостроф)

В этом случае в эхо-области появится (это список перед которым стоит апостроф).

В обоих случаях вы давали команду "вычислить выражение" встроенной в GNU Emacs программе (которая называется интерпретатор Лиспa). Имя интерпретатора Лисп произошло от названия задачи, которую выполняет человек, объясняющий выражение, т.е. "интерпретирующий" его.

Вы можете также вычислить атом, который не является частью списка (не окружен круглыми скобками), и снова интерпретатор Лиспа переведет это из формы, понятной человеку, на язык компьютера. Но до того, как обсуждать это (see section 1.7 Переменные), мы рассмотрим, что сделает интерпретатор Лиспа, когда вы совершите ошибку.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3 Получаем сообщение об ошибке

Чтобы вы не беспокоились из-за случайно совершенной ошибки, мы сейчас дадим команду интерпретатору Лиспа, которая выдаст сообщение об ошибке. Это совсем безвредно; на самом деле мы часто будем выдавать сообщения об ошибках, когда это нам необходимо. Если вы понимаете терминологию, сообщения об ошибках могут быть очень полезными. Вместо того, чтобы называть их сообщения об "ошибках", назовем их "подсказками". Они как указатели для путешественника в незнакомую страну--- разобраться в них сначала тяжело, но когда все ясно, они указывают путь.

Вот, что мы сейчас сделаем: мы вычислим список, перед которым не стоит апостроф, а первый элемент списка не является командой. Этот список мы уже использовали, сейчас мы уберем одиночный апостроф. Расположите курсор после выражения и нажмите C-x C-e;

 
(это список перед которым стоит апостроф)

В этот раз вы увидите, что в эхо-области появится следующее сообщение:

 
Symbol's function definition is void: это

(Также терминал может подать вам звуковой сигнал или мигнуть. Это чтобы привлечь ваше внимание). Сообщение исчезнет как только вы нажмете какую-нибудь клавишу, даже чтобы просто переместить курсор.

Основываясь на том, что мы уже знаем, мы можем полностью понять это сообщение об ошибке. Вы знаете смысл слова `Symbol (символ)'. В этом случае оно относится к первому атому списка, слову `это'. Слово `функция' мы уже упоминали ранее. Это очень важное слово. Для себя мы можем определить его следующим образом: функция --- это набор инструкций для компьютера, которые сообщают ему, что необходимо сделать. (Строго говоря, символ говорит компьютеру, где найти инструкции, но это усложнение мы пока будем игнорировать).

Сейчас мы можем понять сообщении об ошибке: `Symbol's function definition is void: это'. Символу (то есть слову `это'), не соответствует никакой набор инструкций, которые мог бы выполнить компьютер.

Немного странное значение сообщения, `function definition is void' (определение функции --- пусто), объясняется тем, как реализован Emacs Lisp --- то есть, когда с символом не связано никакого определения функции, то место, где должно содержаться это определение --- пусто.

С другой стороны, так как мы смогли успешно сложить 2 и 2, то вычислив выражение (+ 2 2), можно полагать, что с символом + связан некоторый набор инструкций, выполняя которые, компьютер складывает числа, следующие за +.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.4 Имена символов и определения функций

Мы сейчас обсудим одну особенность Лиспа, основываясь на приобретенных нами знаниях: символ, такой как +, сам не является набором инструкций, которые должен выполнить компьютер. Вместо этого символ используется (возможно временно) как указатель на определение или набор инструкций. То есть через имя можно найти эти инструкции. Имена людей используются похожим образом. Ко мне можно обратиться, как к `Боб'; однако`я --- это не буквы `б', `о', `б', а особая жизненная форма, обладающая сознанием. Я --- это не имя, но его можно использовать, чтобы обратиться ко мне.

В Лиспе один и тот же набор инструкций может быть связан с несколькими именами. Например, инструкции для сложения чисел можно связать как с символом plus или плюс, так и с символом + (так и есть в некоторых диалектах Лиспа). Среди людей ко мне можно обращаться как к `Роберту', так и к `Бобу'.

С другой стороны, к символу можно прикрепить только одно определение функции. В противном случае компьютер не смог бы выбрать, какое определение использовать. Если бы так было среди людей, то только один человек в мире мог носить имя `Боб'. Однако определение функции, к которому относится имя, можно легко изменить.

Поскольку Emacs Lisp огромен, то символы принято называть таким образом, чтобы сразу было ясно, к какой части Emacs принадлежит эта функция. Так все имена функций, которые имеют дело с Texinfo начинаются с `texinfo-', а функции, которые имеют дело с электронной почтой, начинаются с `rmail-'.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5 Интерпретатор Лиспа

Основываясь на том, что изучили, мы можем понять, что делает интерпретатор Лиспа, когда мы даем ему команду вычислить выражение. Первым делом интерпретатор смотрит, есть ли апостроф перед списком; если да, то он только возвращает выражение как есть. Если же апострофа нет, то интерпретатор анализирует первый элемент списка и пытается определить по этому символу определение функции. Если функция существует, то интерпретатор выполняет соответствующие ей инструкции. В противном случае он выдает сообщение об ошибке.

Вот как работает интерпретатор Лиспа. Это очень просто. Мы скоро рассмотрим некоторые нюансы, но, то что вы изучили, это основы. Конечно, чтобы написать программу на Лиспе, вам необходимо знать, как определять функцию и связывать ее с именем, и как сделать это, не запутав компьютер и себя.

Вот и первый нюанс. Кроме списков, интерпретатор Лиспа может вычислить символ, перед которым нет апострофа, и который не заключен в круглые скобки. В этом случае интерпретатор Лиспа попытается определить значение символа, считая его переменной. Такая ситуация описана в разделе о переменных в данной главе. (See section 1.7 Переменные.)

Второй нюанс связан с тем, что некоторые функции необычны и работают несколько странно. Их называют особыми формами. Их не очень много и они используются в специальных случаях, например, когда надо определить новую функцию. В следующих нескольких главах мы рассмотрим некоторые из наиболее важных особых форм.

Третий и последний нюанс: если функция, которую анализирует интерпретатор Лиспа, не является особой формой, и если она часть списка, то интерпретатор Лиспа смотрит, есть ли внутри этого списка вложенные списки. Если есть, тогда интерпретатор вначале вычисляет внутренние списки, а затем внешние. Он всегда в первую очередь обрабатывает внутренние списки. Это делается для того, чтобы выяснить, какой они возвращают результат. Этот результат может использоваться в списке верхнего уровня.

Если внутренних списков нет, то интерпретатор вычисляет выражения слева направо, от одного выражения к другому.

1.5.1 Байт-компиляция  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.1 Байт-компиляция

Интерпретатор Лиспа может обрабатывать два вида данных: код, который может прочесть человек (мы и будем рассматривать его в этой книге) и особо обработанный код, не предназначенный для чтения людьми, так называемый байт-компилированный код. Байт-компилированный код выполняется гораздо быстрее чем обычный.

Вы можете перевести обычный код в байт-компилированный, запустив команду для компиляции, например, byte-compile-file. Байт-компилированный код обычно хранится в файлах, имеющих расширение `.elc', а файлы, которые можно читать, имеют расширение `.el'. Вы можете увидеть оба типа файлов в директории `emacs/lisp'.

На практике многое из того, чем мы будем заниматьсяw{ ---} настраивать и расширять Emacsw{ ---} не потребует байт-компиляции; и я не буду обсуждать здесь эту тему. See section `Byte Compilation' in The GNU Emacs Lisp Reference Manual, для более полного описания байт-компиляции.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.6 Вычисление

Когда интерпретатор Лиспа анализирует выражение, то говорят, что он занимается вычислением. То есть интерпретатор "вычисляет выражение". Я уже использовал этот термин несколько раз раньше. Слово пришло из повседневного языка, "выяснить значение или количество; оценить", согласно "Webster's New Collegiate Dictionary".

После вычисления выражения, интерпретатор Лиспа обычно возвращает значение, которое вычислил компьютер, выполняя инструкции, найденные в определении функции; иногда он прерывает выполнение функции и выдает сообщение об ошибке. (Интерпретатор может также выполнить не ту функцию, или он может начать повторять что-нибудь снова и снова --- зайти в так называемый "бесконечный цикл". Эти действия менее обычны, и мы можем проигнорировать их). В основном, интерпретатор возвращает какое-нибудь значение.

Одновременно с возвратом значения интерпретатор может сделать что-нибудь еще, например, переместить курсор или скопировать файл --- этот дополнительный род деятельности называют побочным эффектом. Действия, которые людьми считаются важными, такие как печать результатов, часто являются только "побочными эффектами" для интерпретатора Лиспа. Это может звучать несколько необычно, но позже мы довольно легко научимся использовать побочные эффекты.

В общем вычисление символического выражения обычно заставляет интерпретатор Лиспа возвратить какое-нибудь значение и, возможно, выполнить побочное действие; или прервать выполнение, отобразив соответствующее сообщение об ошибке.

1.6.1 Вычисление внутренних списков  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.6.1 Вычисление внутренних списков

Если мы вычисляем список, внутри которого есть другой список, то при вычислении внешнего списка может использоваться значение, которое возвращается при вычислении внутреннего списка. Это объясняет, почему внутренние выражения вычисляются в первую очередь --- значения, которые они возвращают используются внешними выражениями.

Мы можем исследовать этот процесс, вычислив еще один пример на сложение чисел. Расположите курсор после следующего выражения и нажмите C-x C-e:

 
(+ 2 (+ 3 3))

В эхо-области появится число 8.

Это произошло, потому что интерпретатор Лиспа вначале вычислил внутреннее выражение, (+ 3 3), вычисление которого вернуло значение 6; а затем он вычислил внешнее выражение, которое теперь можно представить как (+ 2 6), вычисление которого вернуло значение 8. Так как в этом выражении больше нечего вычислять, то интерпретатор отобразил это значение в эхо-области.

Сейчас уже довольно легко понять название команды, которая запускается клавишами C-x C-e --- она называется eval-last-sexp. Буквы sexp --- это сокращение от "symbolic expression (символьное выражение)", а eval сокращение от "evaluate". Эта команда означает `вычислить последнее символическое выражение' ("evaluate last symbolic expression").

В порядке эксперимента вы можете сейчас попробовать вычислить это же выражение, расположив курсор на пустой строке сразу за выражением, или внутри него.

Вот копия предыдущего выражения:

 
(+ 2 (+ 3 3))

Если вы расположите курсор в начало пустой строки сразу за этим выражением и нажмете C-x C-e, то в эхо-области также отобразится 8. Сейчас попробуйте расположить курсор внутри выражения. Если вы поставите его за предпоследней скобкой (будет казаться, что курсор поверх последней скобки), в эхо-области появится 6! Это потому что сейчас мы вычислили выражение (+ 3 3).

Теперь расположите курсор сразу за каким-нибудь числом. Нажмите C-x C-e и вы получите само это число. В Лиспе, если вы вычисляете число, то возвращается само это число (этим числа отличаются от символов). Если вы вычислите список, начинающийся с символа +, то интерпретатор вернет вам значение, которое он получит, выполняя инструкции связанные, с этим символом. Если вы попытаетесь вычислить сам этот символ, произойдет что-то другое, и это мы рассмотрим в следующем разделе.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.7 Переменные

В Лиспе к любому символу можно прикрепить какое-нибудь значение, точно также как к нему можно прикрепить функцию. Это две большие разницы. Функция --- это набор инструкций, которые компьютер должен выполнить. С другой стороны, значение --- это нечто, например, число или имя, то что может изменяться (vary) (поэтому такой символ назвали переменной (variable)). Значением символа может быть любое выражение, допустимое в Лиспе --- такое как число, символ, список, или строка. Символ, который имеет значение, часто называют переменной.

С символом одновременно может быть связано и определение функции и какое-нибудь значение. Это два различных понятия. Похоже на то, как имя Кембридж носит город в штате Массачусетс, и с этим именем связана еще кое-какая информация --- многие считают его "крупнейшим центром программирования".

(Кстати, в Emacs Lisp с символом могут быть связаны также список свойств и строка документации; они обсуждаются позже).

Есть другой способ разграничить эти понятия --- представить себе символ как стол с выдвижными ящичками. Определение функции можно положить в один ящик, значение переменной в другой, и так далее. То, что мы положим в ящик для переменной, не повлияет на то, что находится в ящике для функции, и наоборот.

Переменная fill-column --- пример символа, которому соответствует некоторое значение (в каждом буфере GNU Emacs этому символу присвоено какое-нибудь значение, обычно 70 или 72.) Чтобы узнать значение этого символа, вычислите его сами. Если вы читаете это в Info внутри GNU Emacs, вы можете сделать это, расположив курсор за символом и нажав C-x C-e:

 
fill-column

После того, как я нажал C-x C-e, Emacs напечатал в эхо-области число 72. Это значение я присвоил fill-column, когда писал эту книгу. В вашем случае оно может быть другим. Заметьте, что значение, которое вернулось после вычисления переменной, отпечаталось точно так же, как и значение, которое бы вернула функция. С точки зрения интерпретатора Лиспа возвращенное значение есть только возвращенное значение. Какого рода выражение мы вычисляли уже неважно, когда значение известно.

Символу может соответствовать любое значение или, используя жаргон Лиспа, мы можем связать с переменной любое значение --- число, такое как 72; строку, такую как эта; список, такой как (ель сосна дуб); мы можем даже связать с переменной определение функции.

Значение можно связать с переменной несколькими способами. Дополнительную информацию ищите в See section Присваиваем Переменной Значение.

Заметьте, что вокруг слова fill-column не было скобок, когда мы вычислили его, чтобы найти связанное с ним значение. Это потому, что мы не собирались использовать его как функцию. Если бы fill-column было бы первым или единственным символом в списке, то интерпретатор Лиспа попытался бы найти определение функции, связанное с этим символом. Но с fill-column не связана никакая функция. Попробуйте вычислить следующее выражение:

 
(fill-column)

 
Вы получите следующее сообщение об ошибке:

Symbol's function definition is void: fill-column

1.7.1 Сообщение об ошибке для пустого символа  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.7.1 Сообщение об ошибке для пустого символа

Если вы попытаетесь вычислить символ, с которым не связано никакое значение, то вы получите сообщение об ошибке. Вы можете проверить это, продолжив эксперимент с нашим примером на сложение 2 плюс 2. В следующем выражении расположите курсор после +, перед первым числом 2, и нажмите C-x C-e:

 
(+ 2 2)

Вы получите следующее сообщение об ошибке:

 
Symbol's value as variable is void: +

Оно отличается от первого сообщения об ошибке, которое мы видели: `Symbol's function definition is void: это'. Во втором случае с символом, как с переменной, не было связано никакого значения; в первом случае с символом (то есть со словом `это') не было связано определение функции.

В этом эксперименте с + мы заставили интерпретатор Лиспа вычислять символ + как переменную, вместо того, чтобы искать определение функции, связанное с этим символом. Мы сделали это, расположив курсор сразу за символом, а не после скобки, закрывающей список, как было раньше. Поэтому интерпретатор Лиспа вычислил предыдущее s-выражение, которое в нашем случае было символом +.

Так как с + не связано никакого значения, как с переменной, а только определение функции, то мы и получили сообщение об ошибке --- значение символа как переменной пусто.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8 Аргументы

Чтобы понять, как функция получает необходимую ей информацию, давайте снова рассмотрим наш старый добрый пример: 2 + 2. В Лиспе это записывается следующим образом:

 
(+ 2 2)

После вычисления этого выражения, в эхо-области появится число 4. Интерпретатор Лиспа сложил числа, которые следовали за символом +.

Числа, которые складывает функция +, называют аргументами этой функции. Аргументы ---это информация, которую мы сообщили или передали этой функции.

Слово "аргумент" здесь используется в математическом, а не повседневном смысле. Здесь оно обозначает информацию, переданную функции, в нашем случае к функции +. В Лиспе аргументы функции --- это атомы или списки, которые следуют за именем функции. Значения, возвращаемые вычислением этих атомов или списков, передаются в функцию. Разные функции требуют разного числа аргументов, некоторые функции совсем не требуют аргументов.

1.8.1 Типы Данных аргументов  
1.8.2 Аргумент как значение переменной или списка  
                                или списка.
1.8.3 Аргументов может быть много  
                                число аргументов.
1.8.4 А если аргумент неправильного типа  
1.8.5 Функция message  Очень полезная функция.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8.1 Типы Данных аргументов

Типы данных, которые будут передаваться в функцию, зависят от того, какого рода информацию она использует. Аргументы функции, такой как +, должны иметь численные значения, поскольку + складывает числа. Другие функции используют аргументы другого типа.

Например, функция concat связывает вместе, или объединяет две или более строк, чтобы получить одну строку. Ее аргументы строки. Конкатенация двух символьных строк abc, def выдаст в результате строку abcdef. Это можно проверить вычислив следующее:

 
(concat "abc" "def")

Значение, произведенное вычислением, этого выражения равно abcdef.

Аргументами такой функции как substring являются и строки, и числа. Эта функция возвращает часть строки, подстроку своего первого аргумента. Функция требует три аргумента. Первый аргумент --- строка символов, второй и третий аргументы --- это числа, которые отмечают начало и конец строки. Числа --- это количество символов (включая пробелы и знаки пунктуации) от начала строки.

Например если вы вычислите следующее выражение:

 
(substring "Четыре черненьких чумазеньких чертенка." 7 29)

то в эхо-области появится фраза ""черненьких чумазеньких" Аргументами функции substring были строка и два числа.

Отметим, что строка, которую мы передали в функцию substring, это один атом, даже если он был составлен из нескольких слов, разделенных пробелами. Лисп считает все, то заключено между двойными кавычками, частью строки, включая пробелы. Вы можете считать функцию substring как своего рода "разделитель атома" поскольку она извлекает часть из по-другому неделимого атома. Однако substring способна выделить подстроку только из строки, а с другого рода атомами, такими как символы или числа, она не работает.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8.2 Аргумент как значение переменной или списка

Аргументом может быть и символ, который возвращает значение, когда его вычислят. Например, когда мы вычисляем символ fill-column, то он возвращает число. Это число может использоваться в сложении. Поставьте курсор после следующего выражения и нажмите C-x C-e:

 
(+ 2 fill-column)

Значение, которое вы получите будет числом, которое на два больше чем значение fill-column. В моем случае это было 74, поскольку fill-column установлено в 72.

Как мы только что видели, аргументом может быть символ, который возвращает некоторое значение, когда его вычисляют. Кроме того, аргумент может быть списком, который возвращает значение, когда его вычисляют. Например в следующем выражении, аргументами функции concat являются строка "рыжие лисички.", а также список (+2 fill-column).

 
(concat  (+ 2 fill-column) " рыжие лисички.")

Когда я вычислил это выражение, в эхо-области появилось "74 рыжие лисички.". (Обратите внимание на пробелы перед словом `рыжие', чтобы отделить их от числа).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8.3 Аргументов может быть много

Некоторые функции, такие как concat, + или *, принимают произвольное число аргументов. (* --- это символ умножения). Это можно проверить, вычислив каждое из следующих выражений обычным образом. То, что вы увидите в эхо-области, мы будем печатать в тексте после знака `=>', вы можете считать этот знак как "вычисляется в".

В первый раз функции не имеют аргументов:

 
(+)       => 0

(*)       => 1

Теперь у каждой функции один аргумент:

 
(+ 3)     => 3

(* 3)     => 3

Сейчас уже по три аргумента:

 
(+ 3 4 5) => 12

(* 3 4 5) => 60


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8.4 А если аргумент неправильного типа

Если вы попытаетесь передать функции аргумент неправильного типа, то интерпретатор Лиспа отобразит сообщение об ошибке. Например, функция + ожидает, что ее аргументами будут числа. В порядке эксперимента, мы передадим ей вместо числа символ привет, перед которым поставим апостроф . Расположите курсор после следующего выражения и нажмите C-x C-e:

 
(+ 2 'привет)

Когда вы сделаете это, появится сообщение об ошибке. Это произошло когда интерпретатор Лиспа попытался сложить 2 со значением возвращаемым 'привет, но это значение --- сам символ привет, а не число. Складывать можно только числа. Поэтому + и не смог выполнить сложение своих аргументов.

Как обычно сообщение об ошибке содержит полезную информацию, помогающюю исправить ее. Оно гласит:

 
Wrong type argument: integer-or-marker-p, привет

Первая часть сообщения ясна: --- она говорит `Wrong type argument'. (4) Затем идет загадочное жаргонное словечко `integer-or-marker-p'. Этим словом интерпретатор пытается сообщить вам, какого типа аргументы ожидались функцией +.

Символ integer-or-marker-p означает, что интерпретатор Лиспа считал, что информация, представленная аргументом (значение аргумента) это целое число или маркер (особый объект представляющий позицию в буфере). Так как функция + может складывать только числа или маркеры, то интерпретатор проверяет или по другому тестирует типы аргументов на соответствие этим двум типам. (В Emacs положение курсора в буфере записывается в виде маркера. Когда вы ставите метку, нажимая C-@ или C-SPC Emacs запоминает положение в буфере и запоминает его в маркере). Маркер можно упрощенно считать числом символов до точки от начала буфера. В Emacs Лисп + можно использовать для сложения числовых значений маркеров.

Буква `p' в integer-or-marker-p --- наследие практики ранних дней программирования на Лиспе. `p' означает `predicate' (предикат). На жаргоне, используемым первыми исследователями Лиспа, предикатом называли функцию, которая определяла правдиво ли какое-нибудь суждение и возвращала либо истину, либо ложь. Так `p' означает, что integer-or-marker-p --- это имя функции, которая проверяет свой аргумент на соответствие числу или маркеру и возвращает true(истина) или false(ложь). В Лиспе много функций, чьи имена которые оканчиваются на `p', например, zerop, которая тестирует свой аргумент на равенство нулю, или listp --- функция, проверяющая, является ли ее аргумент списком.

И наконец, последняя часть сообщения об ошибке символ привет. Это значение аргумента, который мы передали к +. Если бы мы передали в функцию сложения правильный тип объекта, то значением аргумента было бы число, например, 37, а не символ привет. Но тогда бы вы не получили такого интересного сообщения об ошибке.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8.5 Функция message

Как и +, функция message принимает переменное число аргументов. Ее обычно используют для отображения информационных сообщений пользователю. Мы опишем ее здесь, поскольку это одна из наиболее часто применяемых функций.

Сообщение отображается в эхо-области. Например, вы можете напечатать сообщение в эхо-области, вычислив следующий список:

 
(message "Это сообщение появится в эхо-области!")

Вся строка между двойными кавычками --- это один аргумент, и он появится в эхо-области. (Обратите внимание, что само сообщение появится в эхо-области в двойных кавычках --- это потому что вы видите значение, которое вернула функция message. В большинстве случаев использования функции message в программах, которые мы будем писать, текст, печатаемый в эхо-области --- это побочный эффект, без кавычек).

Однако если в строке найдется символ `%s', то функция message не будет печатать его, а вместо этого просмотрит аргументы, которые следуют за строкой. Она вычислит второй аргумент и напечатает его значение на месте `%s'.

Вы можете проверить это, расположив курсор после следующего выражения и нажав C-x C-e:

 
(message "Имя этого буфера: %s." (buffer-name))

В моем случае в эхо-области появилось, "Имя этого буфера: *info*.". Функция buffer-name вернула имя буфера в виде строки, которую функция message вставила на место %s.

Чтобы напечатать значение в виде десятичного числа, вы должны вместо `%s' использовать `%d'. Например, чтобы напечатать в эхо-области значение fill-column, вычислите следующее:

 
(message "Значение переменной fill-column: %d." fill-column)

Когда я сделал это в моей эхо-области отобразилось "Значение переменной fill-column: 72."

Если в строке больше чем одна `%s', то значение первого аргумента после строки печатается на месте первого появления `%s', а значение второго аргумента --- на месте второго появления `%s', и так далее. Например, если вы вычислите следующее:

 
(message "Ничего себе, в офисе %d %s!"
         (- fill-column 14) "розовых слонов")

тогда в вашей эхо-области появиться довольно забавное сообщение. У меня напечаталось: "Ничего себе, в офисе 58 розовых слонов!".

Выражение (- fill-column 14) при вычислении вернуло число, которое подставилось на место `%d'; а строка в двойных кавычках "розовых слонов", как единый аргумент, вставилась на место `%s'. (То есть строки между двойными кавычками при вычислении возвращает сами себя, как и числа).

Наконец, мы рассмотрим довольно сложный пример, который не только проиллюстрирует вычисление числа, но также покажет, как вы можете использовать вложенные выражения, чтобы получить текст, который заменит `%s':

 
(message "Он увидел %d %s"
         (- fill-column 34)
         (concat "рыжих " "прыгающих "
                 (substring
                  "Шустрые, лисицы весело прыгали." 9 14)))

В этом примере у функции message три аргумента --- строка, "Он увидел %d %s", выражение (- fill-column 32), и выражение, которое начинается с функции concat. Значение, которое возвращается после вычисления (- fill-column 32), вставляется на место `%d', а значение от вычисления списка, где первый элемент concat, вставляется на место `%s'.

Когда я вычислил это выражение, в моей эхо-области появилось сообщение: "Он увидел 38 рыжих прыгающих лисиц".


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.9 Присваиваем переменной значение

Есть несколько способов, для того чтобы переменной можно было присвоить какое-нибудь значение. Например, с помощью функций set и setq. Или с помощью функции let (see section 3.6 let). (На Лисп жаргоне это называют связать переменную со значением.)

В следующем разделе мы не только опишем функции set и setq, но также покажем, как передавать аргументы.

1.9.1 Используя set  Присваиваем значение..
1.9.2 Используя setq  Тоже самое, но удобнее.
1.9.3 Счетчик  Считаем с помощью setq.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.9.1 Используя set

Чтобы связать с символом цветы список '(роза фиалка маргаритка лютик), вычислите следующее выражение, расположив курсор после него и нажав C-x C-e.

 
(set 'цветы  '(роза фиалка маргаритка лютик))

В эхо-области появится список (роза фиалка маргаритка лютик) . Это то, что вернула функция set. Как побочный эффект символ цветы теперь связан со списком --- то есть, символ цветы, можно теперь считать переменной, значением которой является список. (Этот процесс между прочим показывает как побочный эффект для интерпретатора Лиспа для нас является основным. Это потому что каждая функция Лиспа должна возвращать какое-нибудь значение, но побочный эффект имеют только некоторые из них).

После вычисления выражения set, вы можете теперь вычислить символ цветы и посмотреть, какое он вернет значение. Расположите курсор после этого символа и нажмите C-x C-e.

 
цветы

Когда вы вычислите символ цветы, то в эхо-области появиться список (роза фиалка маргаритка лютик).

Вспомним, что если вы вычислите 'цветы --- тот же символ, но на этот раз с апострофом, то перед ним в эхо-области появиться сам этот символ цветы. Ниже символ, перед которым мы поставили апостроф, так что проверьте это:

 
'цветы

Заметьте, что когда вы используете set, вам нужно ставить апостроф перед обеими аргументами set, если вы не хотите, чтобы интерпретатор вычислял их. В нашем случае мы не хотим, чтобы аргументы вычислялись: ни переменная цветы ни список (роза фиалка маргаритка лютик), поэтому перед каждым мы поставили апостроф. (Если бы мы не поставили апостроф перед первым аргументом set, интерпретатор вычислил бы его перед выполнением функции. Если с символом цветы не было бы связано никакое значение, вы бы получили сообщение об ошибке `Symbol's value as variable is void'; если бы вычисление символа цветы все же вернуло бы какое-нибудь значение, тогда функция set попыталась бы связать список именно с этим возвращенным значением. Иногда так и надо делать; но такие ситуации весьма редки.)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.9.2 Используя setq

На практике вы почти всегда ставите апостроф перед первым аргументом set. Сочетание set с первым аргументом, перед которым стоит апостроф, встречается так часто, что была введена особая форма setq. Эта особая форма действует точно также, как и set, но перед первым аргументом не надо ставить апостроф, так как он не вычисляется. Также добавлена еще одна полезная возможность --- setq разрешает вам назначить одновременно значения нескольким переменным в одном выражении.

Чтобы установить значением переменной плотоядные список '(лев тигр леопард) с помощью setq, можно использовать следующее выражение:

 
(setq плотоядные '(лев тигр леопард))

Это тоже самое, что и использование set, но первый аргумент защищен от вычисления setq. (`q' в setq означает quote). С set выражение выглядело бы по-другому:

 
(set 'плотоядные '(лев тигр леопард))

Также setq можно применять для назначения разных величин разным переменным. Первый аргумент связывается со значением второго аргумента, третий аргумент связывается со значением четвертого аргумента и так далее. Например, вы можете использовать следующее выражение, чтобы связать символ деревья со списком деревьев, а список травоядных с символом травоядные:

 
(setq деревья '(сосна ель дуб клен) 
      травоядные '(газель антилопа зебра))

(Это выражение можно было бы поместить в одну строку, но более длинные выражения не поместятся на страницу, кроме того людям легче читать красиво отформатированные списки.)

Хотя я использовал термин `назначить', существует другой способ представления работы функций set и setq --- представьте себе, что функции set и setq заставляют символ указывать на список. Этот последний способ представления очень обычен и в последующих главах я буду использовать его более часто, мы встретим по меньшей мере один символ в имени которого используется `указатель'. Это название выбрано потому, что символ имеет значение --- например, список, связанный с ним; или если по другому выразить, этот символ указывает на список.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.9.3 Счетчик

Здесь пример как использовать setq в счетчике. Вы можете использовать его, чтобы сосчитать сколько раз ваша программа повторилась. Вначале назначьте переменной значение равное нулю, а затем добавляйте по единице каждый раз, когда программа повторяется. Чтобы выполнить это, вам понадобится переменная, которая будет служить счетчиком, и два выражения: первое setq, которое устанавливает счетчик в ноль; и второе --- setq, которое увеличивает счетчик каждый раз, когда его вычисляют.

 
(setq counter 0)                ; Назовем это инициализацией.

(setq counter (+ counter 1))    ; Здесь увеличиваем счетчик.

counter                         ; А это счетчик.

(Текст после `;' --- это комментарий.)

Если вы сначала вычислите первое из этих выражений --- инициализатор (setq counter 0), а затем третье выражение, counter, то в эхо-области появится 0. Если вы вычислите второе выражение для приращения счетчика --- (setq counter (+ counter 1)), то счетчик получит значение 1. Так что если вы снова вычислите counter, то теперь в эхо-области появиться число 1. Каждый раз, когда вы будете вычислять второе выражение, значение счетчика будет увеличиваться.

Когда вы вычисляете выражение приращения, (setq counter (+ counter 1)), интерпретатор Лиспа вначале вычисляет внутренний список --- то есть сложение. Для этого он должен вычислить переменную counter и число 1. После вычисления переменной counter, возвращается ее текущее значение. Затем оно вместе с единицей передается функции +, которая складывает их вместе. Итоговая сумма возвращается как значение этой функции и передается setq, которая связывает переменную counter с этим новым значением. Таким образом и изменяется значение этой переменной.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.10 Резюме

Изучение Лиспа подобно восхождению на холм, где первая часть подъема самая трудная. Мы только что преодолели самую трудную часть --- то что осталось, намного легче и вы почувствуете это, когда мы будем продвигаться вперед.

Если просуммировать:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.11 Упражнения

Вот несколько простых упражнений:


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated on March, 10 2004 using texi2html