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

3. Написание функций

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

Немного о примитивных функциях  
3.1 Особая форма defun  
3.2 Установка определения функции  
3.3 Делаем функцию интерактивной  
3.4 Различные опции для interactive  
3.5 Устанавливаем код надолго  
3.6 let  Создание и инициализация локальных переменных.
3.7 Особая форма if  Что есть if?
3.8 Выражения if--then--else  
3.9 Истина и ложь в Lisp  Что в Лиспе считается истиной?.
3.10 save-excursion  Как запомнить точку, метку и текущий буфер.
3.11 Обзор  
3.12 Упражнения  

Немного о примитивных функциях

Все функции определяются в терминах других функций, кроме нескольких примитивных функций, которые написаны на языке программирования С. Когда вы пишете определение функции, вы пишете его на Emacs Лисп и используете другие функции как строительные блоки. Некоторые из функций, которые вы используете, сами написаны на Emacs Лиспе (возможно даже вами), а некоторые будут примитивами, написанными на языке С. Примитивные функции используются точно так же, как те, которые написаны на Emacs Лиспе, и ведут себя точно таким же образом. Они написаны на языке С, поэтому мы можем легко переносить GNU Emacs на любую платформу, которая поддерживает язык С.

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


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

3.1 Особая форма defun

В Лиспе с символом, таким как mark-whole-buffer, связан некоторый код, который выполняется компьютером, когда вызывается эта функция. Этот код называют определением функции, и он создается вычислением Лисп-выражения, которое начинается с символа defun (это сокращение от define function). Поскольку defun не вычисляет свои аргументы обычным образом, то она называется особой формой.

В следующих разделах мы рассмотрим определения функций, входящие в дистрибутив Emacs, такие как, например, mark-whole-buffer. А в этом разделе мы опишем несколько простых функций так, чтобы вы представляли себе, как это выглядит. Это будут арифметические функции, поскольку они наиболее просты. Некоторые не любят арифметические примеры; однако, если вы один из таких людей, не отчаивайтесь. Едва ли какой нибудь код, который мы будем изучать в оставшихся главах, будет связан с арифметикой или математикой. В основном мы будем обрабатывать текст тем или иным способом.

Определение функции может содержать до пяти частей, которые следуют после слова defun:

  1. Имя символа, с которым это определение функции будет связано.

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

  3. Строка документации, описывающая работу функции. (Формально не обязательна, но настоятельно рекомендуемая).

  4. Необязательное выражение, чтобы сделать функцию интерактивной, чтобы вы могли использовать ее, нажав M-x и набрав имя функции; или связать с ней какое-нибудь сочетание клавиш или клавишу.

  5. Сами инструкции, которые будет выполнять компьютер --- так называемое тело определения функции.

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

 
(defun имя-функции (аргументы...)
  "необязательная-документация..."
  (interactive как-получить-аргументы)     ; необязательно
  тело...)

Как первый пример мы рассмотрим определение функции, которая умножает свой аргумент на 7. (Это пример не интерактивной функции. See section Making a Function Interactive, для получения дополнительной информации).

 
(defun умножить-на-семь (number)
  "Умножить NUMBER на семь."
  (* 7 number))

Это определение начинается со скобок и символа defun, за которым следует имя функции.

За именем функции идет список, который содержит аргументы, передаваемые этой функции. Этот список называется списком аргументов. В нашем случае в списке только один элемент --- символ number. Когда будет выполняться функция, этот символ будет связан со значением аргумента, который передали функции при вызове.

Вместо того, чтобы назвать наш аргумент словом number, я мог бы выбрать любое другое имя. Например, слово multiplicand. Я выбрал слово `number', поскольку это подсказывает, какого рода значение ожидает функция; но слово `multiplicand' тоже весьма неплохо, поскольку оно подскажет, какую роль этот аргумент будет играть в теле нашей функции. Если бы я назвал аргумент foogle, это было бы намного хуже, потому что такое имя не несет никакой полезной информации. Выбор имен это обязанность программиста, и лучше если они будут помогать в понимании функции.

На самом деле вы можете выбрать любое имя, какое пожелаете для символа в списке аргументов, даже имя, которое используется в какой-нибудь другой функции --- имя, которое вы используете для аргумента, локально для этого конкретного определения функции. В этом определении имя относится к одному объекту, а такое же имя вне определения функции --- к совсем другой. Положим, что в вашей семье вас зовут `Коротышка'; когда кто-нибудь из вашей семьи говорит `Коротышка' --- они имеют в виду вас. Но вне вашей семьи, например, в каком нибудь фильме, `Коротышкой' могут звать кого-нибудь еще. Так как имя для символа в списке аргументов локально для данного определения функции, то вы можете изменять значение такого символа внутри тела функции, не изменяя его значения вне функции. Похожего эффекта можно достигнуть использованием выражения let. (See section let.)

Заметьте также, что мы обсуждаем слово `number' в двух различных значениях --- как символ, который появится в программе, и как имя чего-то, что будет заменено чем-то другим во время вычисления функции. В первом случае number --- это символ, а не число; так получилось, что в пределах функции, это переменная, чье значение --- искомое число, но наш главный интерес в нем, как в символе. С другой стороны, когда мы говорим о функции, нас интересует, что за число будет подставлено за место слова number. Чтобы четко это различать, мы используем различные шрифты для отображения слов в этих двух обстоятельствах. Когда мы говорим об этой функции, или о том как это работает, мы соотносимся к числу как number. В теле функции мы пишем это, как number.

За списком аргументов следует строка документации, которая описывает данную функцию. Это ее вы видите, когда нажимаете сочетание клавишC-h f и набираете имя функции. Кстати, если вы пишете строку документации, такую как эту, то вы должны сделать первую строку завершенным предложением, так как некоторые команды, такие как apropos, печатают только первую строку многострочной документации. Так же вы не должны выравнивать вторую строку строки документации, если она у вас появляется, потому что это выглядит странно, когда вы используете сочетание клавиш C-h f (describe-function). Строка документации необязательна, но очень полезна, поэтому ее надо включать во все функции, которые вы создаете.

Третья строка нашего примера --- это тело определения функции. (Большинство определений функций, конечно, намного больше чем это). В нашем случае тело --- это список (* 7 number), которое умножает значение number на 7. (В Emacs Лиспе * функция умножения, как + функция сложения).

Когда вы будете использовать функцию умножить-на-семь, аргумент number будет заменен конкретным числом, которое вы захотите использовать. Ниже пример использования функции умножить-на-семь, но не пытайтесь вычислить ее!

 
(умножить-на-семь 3)

С символом number, который описан в определении функции в предыдущем разделе, при данном вызове функции "связывается" фактическое значение 3. Заметьте, что, хотя number в определении функции был в скобках, но при вызове функции умножить-на-семь, он передавался уже без скобок. Скобки используются в определении функции для того, чтобы компьютер мог отделить список аргументов от тела функции.

Если вы вычислите этот пример, то наверняка получите сообщение об ошибке. (Смелей, пробуйте!) Это потому что хотя мы и написали определение функции, но еще не проинформировали компьютер об этом определении (не внесли определение этой функции в Emacs). Инсталляция функции --- это процесс передачи интерпретатору Лиспа определения функции. Она описана в следующем разделе.


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

3.2 Установка определения функции

Если вы читаете это в Info внутри Emacs, то вы можете попробовать выполнить функцию умножить-на-семь, вначале вычислив определение функции, а затем вычислив (умножить-на-семь 3). Копия определения функции расположена ниже. Поставьте курсор за последней скобкой определения функции и нажмите C-x C-e. Когда вы сделаете это, в эхо-области появится строка умножить-на-семь. (Это означает, что когда вычисляется определение функции, возвращаемым значением является имя определяемой функции). Одновременно с этим мы устанавливаем функцию.

 
(defun умножить-на-семь (number)
  "Умножить NUMBER на семь."
  (* 7 number))

Вычислив defun, вы только что установили умножить-на-семь в Emacs. Эта функция сейчас точно такая же часть Emacs, как и forward-word, или любая другая функция редактирования, которую вы используете. (умножить-на-семь останется в системе, до тех пор пока вы не завершите сессию Emacs. Чтобы загружать код автоматически, когда вы запускаете Emacs, смотрите Installing Code Permanently.)

Вы можете увидеть эффект установки умножить-на-семь вычислив следующий пример. Расположите курсор после выражения и нажмите C-x C-e. В эхо-области появиться число 21.

 
(умножить-на-семь 3)

Если хотите, вы сейчас можете прочесть документацию для этой функции нажав C-h f (describe-function) и затем набрав в мини-буфере имя нашей функции, умножить-на-семь. Когда вы сделает это, на экране появится окно `*Help*', которое отобразит следующее:

 
умножить-на-семь:
Умножить NUMBER на семь.

(Чтобы закрыть окно помощи нажмите C-x 1.)

3.2.1 Изменяем определение функции  


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

3.2.1 Изменяем определение функции

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

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

 
(defun умножить-на-семь (number)       ; Вторая версия.
  "Умножить NUMBER на семь."
  (+ number number number number number number number))

Комментарии следуют после точки с запятой `;'. Согласно синтаксису Лиспа все, что следует после ; --- комментарий. Конец линии --- конец комментария. Чтобы растянуть комментарий на две или более строк, начинайте каждую строку с точки с запятой.

See section Начало файла `.emacs', и section `Comments' in The GNU Emacs Lisp Reference Manual, для дополнительной информации о комментариях.

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

Если просуммировать, именно так вы и пишете код на Emacs Лисп --- сначала вы создаете функцию, устанавливаете ее, тестируете; потом исправляете ошибки или улучшаете и инсталлируете ее вновь.


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

3.3 Делаем функцию интерактивной

Вы можете сделать функцию интерактивной, поместив список, который начинается с особой формы interactive сразу после документации. Пользователь запускает интерактивную функцию нажав M-x и потом набрав имя функции в мини-буфере; или нажав клавишу, к которой эта функция привязана, например, C-n для next-line, или C-x h для mark-whole-buffer.

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

Использование особой формы interactive и способ отобразить значение в эхо-области можно проиллюстрировать интерактивной версией умножить-на-семь.

Вот и текст программы:

 
(defun умножить-на-семь (number)       ; Интерактивная версия.
  "Умножить NUMBER на семь."
  (interactive "p")
  (message "Итог %d" (* 7 number)))

Вы можете установить эту функцию, расположив курсор после определения и нажав C-x C-e. В эхо-области появится имя функции. Теперь вы можете использовать эту функцию, нажав C-u, затем какое-нибудь число и потом M-x умножить-на-семь, а в конце ВВОД. В эхо-области появится фраза `Итог ...', где на месте ... будет стоять итоговое произведение.

То есть, вы можете запустить функцию, подобную этой, двумя способами:

  1. Задав как префикс-аргумент число, передаваемое в функцию, потом нажав M-x и имя функции, например C-u 3 M-x forward-sentence; или,

  2. Нажав клавишу или сочетание клавиш, с которой связана эта функция, например, C-u 3 M-e.

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

(See section Привязки клавиш, чтобы научиться, как связывать команду с клавишей.)

Префикс-аргумент можно передать интерактивной функции, либо нажав клавишу МETA, затем число, например M-3 M-e, или нажав C-u, и потом число, например, C-u 3 M-e. (Если вы просто нажмете C-u по умолчанию префикс-аргумент будет равен 4).

3.3.1 Интерактивная умножить-на-семь.  Интерактивная версия.


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

3.3.1 Интерактивная умножить-на-семь.

Давайте рассмотрим использование особой формы interactive и функции message в интерактивной версии умножить-на-семь. Напомним, как выглядит определение функции:

 
(defun умножить-на-семь (number)       ; Интерактивная версия.
  "Умножить NUMBER на семь."
  (interactive "p")
  (message "Итог %d" (* 7 number)))

В этой функции, выражение (interactive "p") --- список из двух элементов. "p" говорит Emacs передать префикс-аргумент в функцию и использовать его значение как значение аргумента.

Аргумент будет числом. Это значит, что символ number будет связан с этим числом в строке:

 
(message "Итог %d" (* 7 number))

Например, если префикс-аргумент 5, то интерпретатор Лиспа будет вычислять эту строку, как будто бы она выглядит следующим образом:

 
(message "Итог %d" (* 7 5))

(Если вы читаете это в GNU Emacs,то вы можете вычислить это выражение сами). Первым делом интерпретатор вычислит внутренний список --- в нашем случае (* 7 5). Возвращенное значение 35. Затем он вычислит внешний список, передав значения второго и последующих элементов списка функции message.

Как мы уже знаем, message --- это функция Emacs Лисп, специально предназначенная для отображения однострочных сообщений пользователю (See section The message function.). Напомним, что функция message печатает свой первый аргумент в эхо-области как есть, кроме следующих символов `%d', `%s', `%c'. Когда она встречает одну из таких управляющих последовательностей, эта функция вычисляет второй и последующие аргументы и вставляет их значения на место соответствующей управляющей последовательности.

В интерактивной версии умножить-на-семь управляющая строка `%d', которая требует число, а значение, возвращаемое вычислением (*7 5) это число 35. Поэтому число 35 печатается на месте `%d' и конечное сообщение `Итог 35'.

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


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

3.4 Различные опции для interactive

В предыдущем примере умножить-на-семь как аргумент при вызове interactive использовалась строка "p". Это указание интерпретатору Emacs Лиспа передать в функцию численное значение префикс-аргумента, который вы можете задать, нажав либо C-u, либо META и затем набрав какое-нибудь число. В Emacs более 20 символов, предопределенных для использования с interactive. Почти во всех случаях, та или другая из этих опций позволит вам передать интерактивно в функцию всю необходимую информацию. (See section `Code Characters for interactive' in The GNU Emacs Lisp Reference Manual.)

Например, символ `r' заставляет Emacs передать начало и конец области (текущие значения точки и метки) в функцию как два различных аргумента. Его используют следующим образом:

 
(interactive "r")

Другой символ `B' --- указание для Emacs запросить от вас имя буфера, которое будет передано в функцию. В этом случае Emacs отобразит в мини-буфере подсказку, используя строку, которая следует за `B', например, "BДобавить к буферу: ". Emacs не только отобразит подсказку в мини-буфере, но также позволит воспользоваться функцией автодополнения, что здорово помогает (просто наберите первые несколько символов и нажмите TAB).

Функция, которая требует два и более аргумента, может получить информацию для каждого из них, добавляя к строке за interactive различные части. Когда вы сделаете это, информация передается в каждый аргумент в том же самом порядке как они описаны в списке аргументов interactive. В строке каждая часть отделяется друг от друга `\n' --- символом новой строки. Например, если за interactive будет следовать строка "BДобавить к буферу: \nr", то это заставит интерпретатор Лиспа передать в функцию значения точки и метки, а также запросить у вас имя буфера (в целом три аргумента).

В этом случае определение функции будет выглядеть следующим образом: buffer, start и end --- символы, которые interactive свяжет с буфером и текущими значениями начала и конца блока текста:

 
(defun имя-функции (buffer start end)
  "документация..."
  (interactive "BДобавить к буферу: \nr")
  тело-функции...)

(Пробел после двоеточия в подсказке для лучшего отображения в эхо-области. Функция append-to-buffer выглядит точно таким образом. See section The Definition of append-to-buffer.)

Если функции не требуются аргументы, тогда и у interactive их не будет. Такая функция будет содержать только выражение (interactive). Именно так выглядит функция mark-whole-buffer.

Если ни один из существующих буквенных кодов не подходит для вашего приложения, то вы можете передать ваши собственные аргументы к interactive в виде списка. See section `Using Interactive' in The GNU Emacs Lisp Reference Manual, для дополнительной информации об этом методе.


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

3.5 Устанавливаем код надолго

Когда вы устанавливаете функцию, вычисляя ее определение, то она остается внутри Emacs до тех пор, пока вы не завершите работу с ним. Если вы снова запустите Emacs, то функция не будет установлена, до тех пор пока вы не вычислите ее определение снова.

Иногда вам может понадобиться, чтобы функция устанавливалась автоматически, когда вы запускаете новую сессию Emacs. Это можно выполнить несколькими способами:

Наконец, если вы считаете что ваш код может быть полезен всем пользователям Emacs в мире, то вы можете опубликовать его в Internet или послать копию Free Software Foundation. (Когда вы будете это делать, пожалуйста включите в состав кода copyleft notice до того, как публиковать его). Если вы пошлете копию вашего кода Free Software Foundation, nj его могут включить в следующую версию Emacs. В основном именно так Emacs и разрастался в течении многих лет, с помощью пользователей.


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

3.6 let

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

let используют, чтобы связать значение с символом таким образом, чтобы интерпретатор Лиспа не спутал переменную с таким же именем, но определенную вне функции. Чтобы оценить важность этой особой формы, рассмотрим ситуацию.

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

Особая форма let защищает от такого рода путаницы. let создает имя для локальной переменной, которая затеняет любое использование такого же имени вне выражения let. Это как понимание того, что, когда вы сказали `дача', то вы имели в виду свою дачку, а не друга. (Символы используемые в списке аргументов действуют точно таким же образом. See section Особая Форма defun.).

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

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

(`Выполнить' --- это жаргонный термин, который означает вычислить список; он произошел от использования слова в значении `получить практический результат' (Oxford English Dictionary). Поскольку вы вычисляете выражение, чтобы исполнить действие, то `выполнить' используется, как синоним `вычислить'.)

3.6.1 Части выражения let  
3.6.2 Пример выражения let  
3.6.3 Неинициализированные переменные в операторе let  


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

3.6.1 Части выражения let

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

Шаблон для выражения let выглядит следующим образом:

 
(let список_переменных тело...)
Символы в списке переменных --- это переменные, которым особая форма let присваивает начальное значение. Если символ только один, то ему присваивается значение nil; если символ входит в состав двухэлементного списка, то ему назначается значение, которое возвращает интерпретатор Лиспа после вычисления второго элемента списка.

Так, список переменных может выглядеть следующим образом --- (нитки (иголки 3)). В этом случае в выражении let, Emacs свяжет символ нитки со значением nil, а символ иголки со значением 3.

Когда вы будете писать выражение let, то вам надо будет заполнить соответствующими выражениями места в шаблоне для выражения let.

Если список переменных состоит из двухэлементных списков, как это часто и случается, то шаблон для выражения let будет таким:

 
(let ((переменная значение)
      (переменная значение)
      ...)
      тело...)


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

3.6.2 Пример выражения let

В следующем выражении создаются и инициализируются две переменные зебра и тигр. Телом выражения let является список, в котором вызывается функция message.

 
(let ((зебра 'полосаты)
      (тигр 'свирепы))
  (message "Некоторые животные %s, а другие %s."
	   зебра тигр))

Здесь список переменных ((зебра 'полосаты) (тигр 'свирепы)).

Две переменные зебра и тигр. Каждая переменная является первым элементом двухэлементного списка, и ей назначается результат вычисления второго элемента двухэлементного списка. В списке переменных Emacs связывает переменную зебра со значение полосаты, а переменную тигр со значением свирепы. В нашем случае перед символами, которые будут представлять первоначальное значение переменных, стоит апостроф. На их месте мог бы стоять любой список или строка. Основное тело let начинается после списка переменных. В этом случае тело является списком, который использует функцию message, чтобы распечатать строку в эхо-области.

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

 
"Некоторые животные полосаты, а другие свирепы."

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


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

3.6.3 Неинициализированные переменные в операторе let

Если вы не назначили переменным в выражении let какое-нибудь свое первоначальное значение, то им автоматически присваивается значение nil, как в следующем выражении:

 
(let ((береза 3)
      сосна 
      ель
      (дуб 'что-то))
  (message
   "Вот %d переменные со значениями %s, %s, и %s."
   береза сосна ель дуб))

Вот список переменных этого выражения: ((береза 3) сосна ель (дуб 'что-то))

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

 
"Вот 3 переменные со значениями nil, nil, и что-то."

В этом случае Emacs присвоил символу береза число 3, символам сосна и ель значения nil, а символу дуб значение что-то.

Обратите внимание, что в списке переменных let переменные сосна и ель стоят сами по себе, как атомы, и не окружены скобками --- поэтому им при инициализации назначается значение nil. Но дуб в составе списка (дуб 'что-то) и поэтому ему присваивается значение что-то. Аналогично березе присваивается число 3, так как она в списке, где вторым элементом является число 3. (Напомним, что поскольку число вычисляется само собой, то его не надо предварять апострофом. Также число печатается в сообщении на месте `%d', а не `%s'). Вся группа из четырех переменных помещается в скобки, чтобы ограничить их от основного тела let.


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

3.7 Особая форма if

Третья особая форма, которую мы изучим в этой главе, в добавок к defun и let --- это условная if. Эту форму используют когда надо проинструктировать компьютер принять решение. Можно конечно создавать функции и без использования этой формы, но поскольку ее все же используют довольно часто, и она очень важна, то мы рассмотрим ее здесь. Ее используют, например, в определении функции beginning-of-buffer.

Основная идея if --- "если какое-то условие истинно, то тогда выполняем какие-то действия". Если условие ложно, то действия не выполняются. Например, вы можете принять следующее решение "Если будет хорошая погода, то тогда я пойду на пляж!"

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

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

 
(if проверка-истинна-или-ложь
    выполняемые-действия-если-проверка-истинна)

Проверка-истинна-ложь --- это выражение, которое будет вычисляться интерпретатором Лиспа.

Ниже пример, который вы можете вычислить обычным образом. Там проверяется, правда ли, что число 5, больше чем число 4. Так как это похоже на правду, будет отображено следующее сообщение `5 больше чем 4!'.

 
(if (> 5 4)                             ; if-part 
    (message "5 больше чем 4!"))        ; then-part

(Функция > проверяет больше ли ее первый аргумент, чем второй, и возвращает истину в случае успеха).

Конечно, в настоящей программе тестовое условие для выражения if не будет так жестко фиксировано, как в этом примере (> 5 4). Вместо этого по крайней мере одна из переменных будет связана со значением, которое не известно заранее. (Если бы мы знали его заранее, зачем тогда было бы его проверять!)

Например, значение может быть связано с аргументом в определении функции. В следующем определении функции характеристика животного это значение, которое передается в функцию. Если значение аргумента характеристика --- свирепость, тогда печатается сообщение `Это тигр!'; в противном случае вернется nil.

 
(defun тип-животного (характеристика)
  "Напечатать сообщение в эхо-области. В зависимости от ХАРАКТЕРИСТИКИ.
     Если ХАРАКТЕРИСТИКА `свирепость', тогда напечатать предупреждение."
  (if (equal характеристика 'свирепость)
      (message "Это тигр!")))

Если вы читаете это внутри Emacs, то вы можете вычислить определение этой функции как обычно, чтобы установить ее в Emacs, а потом вычислить два следующих выражения:

 
(тип-животного 'свирепость)

(тип-животного 'зебра)

Когда вы вычислите (тип-животного 'свирепость), в эхо-области появится следующее сообщение: "Это тигр!"; а когда вы вычислите (тип-животного 'зебра), в эхо-области будет напечатано nil.

3.7.1 Подробное рассмотрение функции type-of-animal  Пример выражения if.


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

3.7.1 Подробное рассмотрение функции type-of-animal

Давайте рассмотрим функцию тип-животного подробнее.

Эта функция написана стандартным образом путем заполнения соответствующих мест в двух шаблонах --- один для определения функции defun, а второй для выражения if.

Шаблон для каждой неинтерактивной функции:

 
(defun имя-функции (список-аргументов)
  "документация..."
  тело...)

Части функции, соответствующие шаблону, выглядят следующим образом:

 
(defun тип-животного (характеристика)
  "Напечатать сообщение в эхо-области. В зависимости от ХАРАКТЕРИСТИКИ.
Если ХАРАКТЕРИСТИКА `свирепость', тогда напечатать предупреждение."
    тело:  if выражение)

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

Шаблон для выражения if такой:

 
(if проверка-истинна-ложь
    выполняемые-действия-если-проверка-истинна)

В определении функции тип-животного, настоящий код для if выглядит следующим образом:

 
(if (equal характеристика 'свирепость)
    (message "Это тигр!")))

Здесь проверка-истинна-ложь это выражение:

 
(equal характеристика 'свирепость)

В Лиспе, функция equal проверяет на равенство два своих аргумента. В нашем случае второй аргумент символ 'свирепость, а первый --- значение символа характеристика (другими словами аргумент, который передали в нашу функцию.)

В начале мы передали в функцию тип-животного аргумент 'свирепость. Поскольку свирепость равна свирепость, то вычисление выражения (equal характеристика 'свирепость), вернуло истинное значение. Когда это произошло, if вычислило свой второй аргумент или then-часть: (message "Это тигр!")

Во втором примере мы передали функции тип-животного аргумент зебра. зебра не равна свирепости, поэтому then-часть не вычислялось и выражение if вернуло nil.


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

3.8 Выражения if--then--else

У выражения if может быть необязательный третий аргумент, который называют else-часть, это на тот случай, когда проверка истинна-ложь окончится неудачей, то есть вернет ложь. Когда это происходит, второй аргумент или then-часть не вычисляется, а вычисляется третья часть или else-часть выражения if. Вы можете считать это альтернативой для дождливого дня в следующем решении `если будет солнечный день, тогда пойду на пляж, иначе буду читать книгу!'.

Слово "else" не надо записывать согласно синтаксису Лиспа; else-часть в выражении if идет сразу за then-частью. Обычно, else-часть записывают на отдельной строке и выравнивают немного левее чем then-часть:

 
(if проверка-истинна-ложь
    выполняемые-действия-если-проверка-истинна)
  действия-выполняемые-если-проверка-ложна)

Например, следующее выражение if напечатает сообщение `4 не больше чем 5' если вы вычислите его обычным образом:

 
(if (> 4 5)                             ; if-часть
    (message "5 больше чем 4!")         ; then-часть
  (message "4 не больше чем 5!"))       ; else-часть

Обратите внимание, что разные уровни выравнивания сразу позволяют отделить then-часть от else-части. (В GNU Emacs существуют несколько команд, которые автоматически выравнивают выражения if. See section GNU Emacs Помогает Вам Программировать.)

Вы можете расширить функцию тип-животного, чтобы включить else-часть, просто добавив ее к выражению if.

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

 
(defun тип-животного (характеристика)	; Вторая версия
  "Напечатать сообщение в эхо-области. В зависимости от ХАРАКТЕРИСТИКИ.
     Если ХАРАКТЕРИСТИКА `свирепость, тогда напечатать предупреждение."
  (if (equal характеристика 'свирепость)
      (message "Это тигр!")
    (message "Оно не свирепое!")))

 
(тип-животного 'свирепость)

(тип-животного 'зебра)

Когда вы вычислите (тип-животного 'свирепость), в эхо-области будет напечатано следующее сообщение "Это тигр!", а когда вы измените аргумент на зебра, вы увидите "Оно не свирепое!".

(Конечно, если характеристика будет жестокость, сообщение "Оно не свирепое!" может быть обманчивым! Когда вы пишете реальные программы, вы всегда должны принимать в рассмотрение возможность таких неожиданных аргументов и соответствующим образом реагировать на них).


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

3.9 Истина и ложь в Lisp

Существует важный аспект, касающийся проверки на истинность в выражении if. Пока мы говорили об `истине' и `лжи' как о значении предикатов, как будто это какие-то новые объекты в Лиспе. На самом деле `ложь' --- это наш старый друг nil. Все остальное --- это `истина'.

Выражение, которое осуществляет проверку условия, возвращает true, если результат вычисления проверочного условия не nil. Другими словами, результат проверки считается истинным, если возвращенное значение число такое как 47; строка, например "привет"; или символ (любой кроме nil), например цветы; или список, или даже буфер!

До того как проиллюстрировать выше сказанное, мы поподробнее объясним значение nil.

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

В Лиспе, любое значение, которое не равно nil (или не пустой список) считается истиной. Это означает, что если вычисление проверки истинна-ложь в выражении if вернет что угодно кроме пустого списка, то это будет считаться истиной. Например, если на месте проверяемого условия будет число, то при вычислении оно вернет само себя, именно как это делают числа, когда их вычисляют. В этом случае проверка истина-ложь будет считаться выполненной успешно. Проверка будет считаться неудачной только тогда, когда при вычислении проверяемого выражения оно вернет nil или пустой список.

Вы можете проверить это, вычислив два следующих примера.

В первом из них, в проверке выражения if вычисляется число 4, и так как она возвращает сама себя, это считается истиной, и поэтому вычисляется then-часть выражения if --- в результате в эхо-области появляется `истина'. Во втором примере nil означает ложь --- поэтому вычисляется else-часть выражения if, и в эхо-области печатается `ложь'.

 
(if 4
    'истина
  'ложь)

(if nil
    'истина
  'ложь)

Кстати, если какое-нибудь полезное значение не доступно для возвращения при тестовом условии, которое завершается успешно, то тогда интерпретатор Лиспа вернет символ t, который в таких случаях означает истина. Например, выражение (> 5 4) вернет t в результате вычисления, как вы можете проверить сами:

 
(> 5 4)

С другой стороны, эта функция вернет nil, если проверка завершится неудачей.

 
(> 4 5)


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

3.10 save-excursion

Функция save-excursion --- четвертая и последняя особая форма, которую мы обсудим в этой главе.

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

До обсуждения save-excursion может быть полезным вначале вспомнить что такое точка (point) и метка (mark) в GNU Emacs. Точка (point) --- это текущее положение курсора. Там где курсор, там и точка. Если быть более точным на терминалах, где курсор кажется поверх символа, точка расположена, как раз перед этим символом. В Emacs Лиспе, точка --- это целое число. Первый символ в буфере --- число один, второй --- число два, и так далее. Функция point возвращает текущую позицию курсора как число. В каждом буфере свое собственное значение для точки.

Метка (mark) --- это другая позиция в буфере; ее можно установить с помощью команды, такой как C-SPC (set-mark-command). Если метка установлена, то вы можете использовать команду C-x C-x (exchange-point-and-mark). Это заставит курсор переместиться к метке и установить метку на месте прошлого положения курсора. В добавок, если вы установите другую метку, то позиция предыдущей будет сохранена в кольце меток. Вы можете переместить курсор на место сохраненной метки, нажимая C-u C-SPC один или несколько раз.

Часть буфера между точкой и меткой называется блок (region). Много команд работают над блоком текста, например, center-region, count-lines-region, kill-region и print-region.

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

В Emacs, функции часто перемещают точку в процессе внутренней работы, хотя пользователь и не ожидает этого. Например так делает функция count-lines-region. Чтобы оградить пользователя от раздражающих, и с его точки зрения ненужных прыжков курсора, часто используют функцию save-excursion, чтобы сохранять положение точки и метки в местах, ожидаемых пользователем. Использование save-excursion --- хороший стиль программирования.

Чтобы убедиться, что все в порядке, save-excursion восстанавливает значения точки и метки, даже если что-то идет не так в теле функции (или если быть более точным и использовать надлежащий жаргон в "случае ненормального завершения" (abnormal exit)). Эта возможность может быть очень полезной.

Кроме восстановления значений точки и метки, save-excursion запоминает текущий буфер и также восстанавливает его. Это означает, что вы можете написать программу, которая меняет буфер в процессе своей работы, и потом save-excursion переключит вас обратно в первоначальный буфер. Именно так save-excursion используется в функции append-to-buffer. (See section The Definition of append-to-buffer.)

3.10.1 Шаблон для выражения save-excursion  


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

3.10.1 Шаблон для выражения save-excursion

Шаблон для использования в программах выражения save-excursion очень прост:

 
(save-excursion
  тело...)

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

Более детально шаблон для save-excursion выглядит следующим образом:

 
(save-excursion
  первое-выражение-в-теле
  второе-выражение-в-теле
  третье-выражение-в-теле
   ...
  последнее-выражение-в-теле)

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

В программах на Emacs Лиспе выражение save-excursion часто помещают в тело выражения let. Это выглядит следующим образом:

 
(let список-переменных
  (save-excursion
    тело...))


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

3.11 Обзор

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

eval-last-sexp
Вычислить последнее символическое выражение перед текущим положением курсора. Возвращенное значение будет напечатано в эхо-области, если эта функция запущена без префикс-аргумента; если функция запущена с префикс-аргументом, то результат печатается в текущем буфере. Эта команда обычно привязана к сочетанию клавиш C-x C-e.

defun
Define function (Определить функцию). Эта особая форма может включать до пяти частей --- имя, шаблон для аргументов, передаваемых в функцию, необязательную документацию, необязательное интерактивное объявление, и само тело функции.

Например:

 
(defun back-to-indentation ()
  "Переместить точку к первому видимому символу на линии."
  (interactive)
  (beginning-of-line 1)
  (skip-chars-forward " \t"))

interactive
Объявить интерпретатору, что эта функция может использоваться интерактивно. За этой особой формой может следовать строка, состоящая из нескольких частей, о том, как передать информацию в эту функцию. В этой строке могут так же содержаться подсказки отображаемые в эхо-области. Части строки разделяются друг от друга символами новой строки `\n'.

Наиболее часто используемые символы:

b
Имя существующего буфере.

f
Имя существующего файла.

p
Числовой префикс-аргумент. (Обратите внимание, что `p' набран в нижнем регистре).
r
Точка и метка, как два числовых аргумента, самое маленькое первым. Это единственный символ, который сразу описывает два последовательных аргумента, а не один.

See section `Code Characters for `interactive'' in The GNU Emacs Lisp Reference Manual, для более полного списка символов, предопределенных для interactive.

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

Например,

 
(let ((foo (buffer-name))
      (bar (buffer-size)))
  (message
   "В этом буфере %s ровно %d characters."
   foo bar))

save-excursion
Запоминает значение точки, метки и текущего буфера перед вычислением тела этой особой формы. Затем восстанавливает их значения к первоначальным.

Например,

 
(message "Мы на расстоянии %d символов от начала буфера."
         (- (point)
            (save-excursion
              (goto-char (point-min)) (point))))

if
Вычисляет первый аргумент особой формы; если результат --- истинна, вычисляет второй аргумент; иначе вычисляет третий аргумент, если он существует.

Особая форма if называется условной формой. В Emacs Лиспе существуют и другие условные формы, но if наиболее часто используемая.

Например,

 
(if (string= (int-to-string 19)
             (substring (emacs-version) 10 12))
    (message "Это 19 версия Emacs")
  (message "Это не 19 версия Emacs"))

equal
eq
Проверяют два объекта на равенство. equal возвращает истину если два объекта имеют одинаковую структуру и содержание. Другая функция eq возвращает истину, если два аргумента на самом деле один и тот же объект.

<
>
<=
>=
Функция < проверяет, меньше ли ее первый аргумент чем второй. Соответственно функция > проверяет больше ли ее первый аргумент чем второй. <= проверяет меньше или равен первый аргумент второго и >= соответственно больше либо равен первый аргумент второму. Эти функции работают только с численными аргументами.

message
Печатает сообщение в эхо-области. Длина сообщения ограничена только одной строкой. Первый аргумент --- это строка, которая может содержать символы `%s', `%d', `%c', на месте которых будут подставлены последующие аргументы функции message. Аргумент, подставляемый на место `%s', должен быть строкой или символом; на место `%d' подставляется число. Аргумент, который используется с `%c' тоже должен быть числом, оно будет напечатано как код для ASCII символа.

setq
set
Функция setq устанавливает значением своего первого аргумента значение второго аргумента. Первый аргумент setq не вычисляется автоматически. Эту функцию можно использовать и сразу с несколькими аргументами. Другая функция set принимает только два аргумента, и после вычисления обоих назначает значению, возвращенному первым аргументом, значение, возвращенное вычислением второго аргумента.

buffer-name
Используется без аргумента, возвращает имя буфера в виде строки.

buffer-file-name
Используется без аргумента, возвращает имя файла, связанного с данным буфером.

current-buffer
Возвращает текущий активный буфер Emacs --- это необязательно должен быть буфер, который отображен на экране.

other-buffer
Возвращает недавно выбранный буфер.

switch-to-buffer
Устанавливает буфер, который задан как аргумент активным для Emacs и одновременно отображает его в текущем окне. Обычно эта команда связана с C-x b.

set-buffer
Переключает внимание Emacs на другой буфер. Не изменяет содержимое текущего окна Emacs.

buffer-size
Возвращает число символов в текущем буфере.

point
Возвращает значение текущей позиции курсора как целое, считая число символов с начала буфера.

point-min
Возвращает минимально возможное значение точки в текущем буфере. Обычно 1, если не включено сужение.

point-max
Возвращает максимально возможное значение точки в текущем буфере. Обычно конец буфера, если не включено сужение.


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

3.12 Упражнения


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

This document was generated on March, 10 2004 using texi2html