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

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

Специальная форма: if условие then-форма else-формы…

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

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

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

(if nil
    (print 'true)
  'very-false)
⇒ very-false
Макрос: when условие then-формы…

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

(when условие a b c)

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

(if условие (progn a b c) nil)
Макрос: unless условие формы…

Это вариант if, в котором отсутствует then-форма:

(unless условие a b c)

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

(if условие nil
   a b c)
Специальная форма: cond предложение…

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

(условие тела-формы…)

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

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

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

(условие)

Тогда, если условие при проверке не равно nil, форма cond возвращает значение условия.

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

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

(cond ((numberp x) x)
      ((stringp x) x)
      ((bufferp x)
       (setq temporary-hack x) ; несколько тел-форм
       (buffer-name x))        ; в одном предложении
      ((symbolp x) (symbol-value x)))

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

(setq a 5)
(cond ((eq a 'hack) 'foo)
      (t "по умолчанию"))
⇒ "по умолчанию"

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

Любую условную конструкцию можно выразить с помощью 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-форма else-формы...

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

Каждый элемент varlist имеет вид (символ форма-значения): форма-значения вычисляется, и символ локально связывается с результатом. Связывания выполняются последовательно, как в let* (см. Локальные переменные). В частном случае символ может быть опущен, если важен только результат проверки формы-значения: форма-значения вычисляется и проверяется на nil, но её значение не связывается.

Макрос: when-let* varlist then-формы...

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

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

Макрос: and-let* varlist then-формы...

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

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

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

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

Макрос: while-let спецификация then-формы...

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

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

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