Условные управляющие структуры обеспечивают выбор между альтернативами. В Emacs Lisp
существует пять условных форм: if, которая во многом аналогична таковой в
других языках; when и unless, являющиеся вариантами
if; cond, представляющая собой обобщённый оператор выбора;
и pcase, являющаяся обобщением cond
(см. Условная конструкция с сопоставлением с образцом).
if выбирает между then-формой и else-формами
на основе значения условия. Если вычисленное условие не равно
nil, вычисляется then-форма и возвращается её результат.
В противном случае else-формы вычисляются в текстовом порядке, и
возвращается значение последней из них. (Часть else в if является
примером неявного progn. См. Последовательное выполнение.)
Если условие имеет значение nil, а else-формы не заданы,
if возвращает nil.
if является специальной формой, потому что ветвь, которая не выбрана,
никогда не вычисляется — она игнорируется. Таким образом, в этом примере
true не выводится, поскольку print никогда не вызывается:
(if nil
(print 'true)
'very-false)
⇒ very-false
Это вариант if, в котором отсутствуют else-формы
и может быть несколько then-форм. В частности,
(when условие a b c)
полностью эквивалентно
(if условие (progn a b c) nil)
Это вариант if, в котором отсутствует then-форма:
(unless условие a b c)
полностью эквивалентно
(if условие nil a b c)
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))
Существует несколько вариаций на эту тему, и они кратко описаны ниже.
Вычисляет каждое связывание в varlist, останавливаясь, если значение связывания
равно nil. Если все не равны nil, возвращает значение
then-формы, в противном случае — последнюю форму в else-формах.
Каждый элемент varlist имеет вид (символ форма-значения): форма-значения вычисляется, и символ
локально связывается с результатом. Связывания выполняются последовательно, как в let*
(см. Локальные переменные). В частном случае символ может быть
опущен, если важен только результат проверки формы-значения:
форма-значения вычисляется и проверяется на nil, но её значение
не связывается.
Вычисляет каждое связывание в varlist, останавливаясь, если значение связывания
равно nil. Если все не равны nil, возвращает значение последней
формы в then-формах.
varlist имеет ту же форму, что и в if-let*: каждый элемент
varlist имеет вид (символ форма-значения),
в котором форма-значения вычисляется, а символ локально связывается
с результатом. Связывания выполняются последовательно, как в let* (см. Локальные переменные). В частном случае символ может быть опущен, если важен только
результат проверки формы-значения: форма-значения вычисляется
и проверяется на nil, но её значение не связывается.
Вычисляет каждое связывание в varlist, останавливаясь, если значение связывания
равно nil. Если все не равны nil, возвращает значение последней
формы в then-формах или, если then-формы отсутствуют, возвращает
значение последнего связывания.
varlist имеет ту же форму, что и в if-let*: каждый элемент
varlist имеет вид (символ форма-значения),
в котором форма-значения вычисляется, а символ локально связывается
с результатом. Связывания выполняются последовательно, как в let* (см. Локальные переменные). В частном случае символ может быть опущен, если важен только
результат проверки формы-значения: форма-значения вычисляется
и проверяется на nil, но её значение не связывается.
Некоторые программисты на Lisp придерживаются соглашения, что and и
and-let* используются для форм, вычисляемых ради возвращаемого значения, а
when и when-let* — для форм, вычисляемых ради побочного эффекта,
а возвращаемые значения игнорируются.
Существует аналогичный макрос для выполнения цикла до тех пор, пока одно из связываний
не примет значение nil:
Последовательно вычисляет каждое связывание в спецификации, останавливаясь, если значение связывания
равно nil. Если все не равны nil, выполняет then-формы,
затем повторяет цикл. Обратите внимание, что при повторении цикла
формы-значения в спецификации вычисляются заново, и связывания
устанавливаются заново.
varlist имеет ту же форму, что и в if-let*: каждый элемент
varlist имеет вид (символ форма-значения),
в котором форма-значения вычисляется, а символ локально связывается
с результатом. Связывания выполняются последовательно, как в let* (см. Локальные переменные). В частном случае символ может быть опущен, если важен только
результат проверки формы-значения: форма-значения вычисляется
и проверяется на nil, но её значение не связывается.
Возвращаемое значение while-let всегда равно nil.