Spec-Zone.ru › Elisp

Условные конструкции

Условные управляющие конструкции выбирают один из вариантов. В Emacs Lisp есть пять условных форм: if, которая во многом похожа на конструкции в других языках; when и unless, являющиеся вариантами if; cond, представляющая собой обобщённую конструкцию выбора; и pcase, являющаяся обобщением cond (см. Условная конструкция с сопоставлением с образцом).

Специальная форма: if condition then-form else-forms…

if выбирает между then-form и else-forms в зависимости от значения condition. Если вычисленное значение condition отлично от nil, вычисляется then-form и возвращается результат. В противном случае else-forms вычисляются в текстовом порядке, и возвращается значение последней формы. (Часть else в if — это пример неявного progn. См. Последовательное выполнение.)

Если значение condition равно nil и else-forms не заданы, if возвращает nil.

if — специальная форма, поскольку невыбранная ветвь никогда не вычисляется — она игнорируется. Поэтому в этом примере true не выводится, так как print никогда не вызывается:

(if nil
    (print 'true)
  'very-false)
⇒ very-false
Макрос: when condition then-forms…

Это вариант if без else-forms и, возможно, с несколькими then-forms. В частности,

(when condition a b c)

полностью эквивалентна

(if condition (progn a b c) nil)
Макрос: unless condition forms…

Это вариант if без then-form:

(unless condition a b c)

полностью эквивалентна

(if condition nil
   a b c)
Специальная форма: cond clause…

cond выбирает один из произвольного числа вариантов. Каждое clause в cond должно быть списком. CAR этого списка — это condition; оставшиеся элементы, если они есть, — это body-forms. Таким образом, предложение выглядит так:

(condition body-forms...)

cond проверяет предложения в текстовом порядке, вычисляя condition каждого предложения. Если значение condition отлично от nil, предложение выполняется успешно; тогда cond вычисляет его body-forms и возвращает значение последней из форм body-forms. Все оставшиеся предложения игнорируются.

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

Предложение также может выглядеть так:

(condition)

В таком случае, если при проверке condition отлично от nil, форма cond возвращает значение condition.

Если результат вычисления каждого condition равен nil, то есть ни одно предложение не выполняется, cond возвращает nil.

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

(cond ((numberp x) x)
      ((stringp x) x)
      ((bufferp x)
       (setq temporary-hack x) ; multiple body-forms
       (buffer-name x))        ; in one clause
      ((symbolp x) (symbol-value x)))

Часто требуется выполнить последнее предложение, если ни одно из предыдущих не выполнилось успешно. Для этого в качестве condition последнего предложения используют t, например: (t body-forms). Форма t вычисляется в t, которое никогда не бывает nil, поэтому это предложение никогда не завершается неудачей, если только до него доходит выполнение cond. Например:

(setq a 5)
(cond ((eq a 'hack) 'foo)
      (t "default"))
⇒ "default"

Это выражение cond возвращает foo, если значение a равно hack, а в противном случае возвращает строку "default".

Любую условную конструкцию можно выразить с помощью cond или if. Поэтому выбор между ними зависит от стиля. Например:

(if a b c)
≡
(cond (a b) (t c))

Условные конструкции удобно сочетать с привязкой переменных. Часто требуется вычислить значение, а затем выполнить с ним какое-либо действие, если оно отлично от nil. Самый простой способ — просто написать, например:

(let ((result1 (do-computation)))
  (when result1
    (let ((result2 (do-more result1)))
      (when result2
        (do-something result2)))))

Поскольку это очень распространённый шаблон, в Emacs есть несколько макросов, которые упрощают его использование и делают код более читаемым. Вместо этого приведённый выше код можно записать так:

(when-let* ((result1 (do-computation))
            (result2 (do-more result1)))
  (do-something result2))

Существует несколько вариантов этого подхода, описанных ниже.

Макрос: if-let* varlist then-form else-forms...

Вычисляет каждую привязку в varlist, останавливаясь, если значение привязки равно nil. Если все значения отличны от nil, возвращает значение then-form, иначе — последней формы в else-forms.

Каждый элемент varlist имеет вид (symbol value-form): value-form вычисляется, а symbol локально связывается с результатом. Привязки создаются последовательно, как в let* (см. Локальные переменные). Если важен только результат проверки value-form, укажите _ вместо symbol. Тогда value-form вычисляется и проверяется на nil, но его значение не привязывается. В особом случае элемент varlist может иметь вид просто symbol, что означает проверку текущей привязки symbol на nil.

Макрос: when-let* varlist then-forms...

Вычисляет каждую привязку в varlist, останавливаясь, если значение привязки равно nil. Если все значения отличны от nil, возвращает значение последней формы в then-forms.

varlist имеет ту же форму, что и в if-let*: каждый элемент varlist имеет вид (symbol value-form), где value-form вычисляется, а symbol локально связывается с результатом. Привязки создаются последовательно, как в let* (см. Локальные переменные). Если важен только результат проверки value-form, укажите _ вместо symbol. Тогда value-form вычисляется и проверяется на nil, но его значение не привязывается. В особом случае элемент varlist может иметь вид просто symbol, что означает проверку текущей привязки symbol на nil.

Макрос: and-let* varlist then-forms...

Вычисляет каждую привязку в varlist, останавливаясь, если значение привязки равно nil. Если все значения отличны от nil, возвращает значение последней формы в then-forms или, если then-forms отсутствуют, значение последней привязки.

varlist имеет ту же форму, что и в if-let*: каждый элемент varlist имеет вид (symbol value-form), где value-form вычисляется, а symbol локально связывается с результатом. Привязки создаются последовательно, как в let* (см. Локальные переменные). Если важен только результат проверки value-form, укажите _ вместо symbol. Тогда value-form вычисляется и проверяется на nil, но его значение не привязывается. В особом случае элемент varlist может иметь вид просто symbol, что означает проверку текущей привязки symbol на nil.

Некоторые программисты на Lisp придерживаются соглашения, согласно которому and и and-let* используются для форм, вычисляемых ради возвращаемого значения, а when и when-let* — для форм, вычисляемых ради побочного эффекта, при этом возвращаемые значения игнорируются.

С точки зрения стиля лучше не использовать эти макросы для привязки значений, которые всегда будут отличны от nil. Например, предположим, что foo-p и bar-p могут возвращать nil, и в этом случае выполнение кода не должно продолжаться. Вместо

(when-let* ((foo-val (foo-p))
            (num (+ 2 foo-val))
            (bar-val (bar-p num)))
  ...)

лучше использовать

(when-let* ((foo-val (foo-p)))
  (let ((num (+ 2 foo-val))
    (when-let* ((bar-val (bar-p num)))
      ...)))

так как так яснее, что выполнение кода зависит от того, отличны ли foo-val и bar-val от nil, но не зависит непосредственно от num, которое к тому же никогда не может быть nil.

Макроса cond-let* нет, поскольку расширить структуру, общую для if-let*, when-let* и and-let*, на случай cond непросто. Однако для достижения похожего результата можно использовать предложения bind-and* макроса cond* (см. Макрос cond*):

(cond* ((bind-and* (result1 (do-computation))
                   (result2 (do-more result1)))
        (do-something result2))
       ...)

Существует похожий макрос для выполнения цикла до тех пор, пока одна из привязок не вычислится в nil:

Макрос: while-let spec then-forms...

По очереди вычисляет каждую привязку в spec, останавливаясь, если значение привязки равно nil. Если все значения отличны от nil, выполняет then-forms, а затем повторяет цикл. Обратите внимание: при повторении цикла формы value-forms в spec вычисляются заново, а привязки создаются снова.

varlist имеет ту же форму, что и в if-let*: каждый элемент varlist имеет вид (symbol value-form), где value-form вычисляется, а symbol локально связывается с результатом. Привязки создаются последовательно, как в let* (см. Локальные переменные). Если важен только результат проверки value-form, укажите _ вместо symbol. Тогда value-form вычисляется и проверяется на nil, но его значение не привязывается. В особом случае элемент varlist может иметь вид просто symbol, что означает проверку текущей привязки symbol на nil.

Возвращаемое значение while-let всегда равно nil.

Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Conditionals.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API