条件制御構造は、複数の選択肢から一つを選びます。Emacs Lispには5つの条件形式があります。他の言語とほぼ同様のif、ifのバリエーションであるwhenとunless、一般化されたcase文であるcond、そしてcondを一般化したpcaseです(パターンマッチング条件を参照)。
if は、condition の値に基づいて then-form と else-forms
のいずれかを選択します。評価された condition が nil 以外の場合、then-form
が評価され、その結果が返されます。それ以外の場合は、else-forms
がテキスト順に評価され、最後のものの値が返されます。(if の else 部分は、暗黙的な progn の一例です。シーケンシング を参照してください。)
condition が nil の値を持ち、else-forms が指定されていない場合、if は
nil を返します。
if
は、選択されなかった分岐が決して評価されず—無視されるため、特殊な形式となります。したがって、この例では print が呼び出されないため、true
は出力されません:
(if nil
(print 'true)
'very-false)
⇒ very-false
これはifの変種であり、else-formsが存在せず、then-formsが複数存在する場合がある。具体的には、
(when condition a b c)
と完全に等価である
(if condition (progn a b c) nil)
これは then-form が存在しない if
のバリアントです:
(unless condition a b c)
と完全に等価である
(if condition nil a b c)
cond は任意の数の選択肢から一つを選びます。cond 内の各 clause
はリストである必要があります。このリストの 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 を返す。
以下の例には4つの節があり、それぞれ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 式は、a の値が hack であれば foo を、そうでなければ文字列
"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))
このテーマにはいくつかのバリエーションがあり、以下に説明します。
varlist内の各バインディングを評価し、バインディング値がnilであればそこで停止する。すべてがnil以外であればthen-formの値を、そうでなければelse-forms内の最後のフォームを返す。
varlist の各要素は (symbol value-form)
の形式をとります。value-form が評価され、その結果に symbol がローカルにバインドされます。バインドは let*
のように順次行われます(ローカル変数
を参照)。value-form の判定結果のみに関心がある場合は、symbol の代わりに _ を使用してください。すると
value-form が評価され、nil
に対してチェックが行われますが、その値はバインドされません。特殊なケースとして、varlist
のエントリが単に symbol
という形式になる場合があり、これは nil
に対してシンボルの現在のバインドを確認することを意味します。
varlist内の各バインディングを評価し、バインディング値がnilであればそこで終了する。すべてnilでない場合は、then-forms内の最後のフォームの値を返す。
varlist は if-let*
と同じ形式です。varlist の各要素は (symbol value-form)
の形式をとり、その中で value-form が評価され、symbol がその結果にローカルに束縛されます。束縛は let* のように順次行われます(ローカル変数 を参照)。value-form のテスト結果のみに関心がある場合は、symbol
に _ を使用してください。すると value-form が評価され、nil
に対してチェックされますが、その値は束縛されません。特殊なケースとして、varlist
のエントリが単に symbol
の形式をとることがあり、これは nil に対してシンボルの現在の束縛を確認することを意味します。
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
の形式をとることがあり、これは 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)))
...)))
because this makes it clearer that the execution of the code is conditional
on foo-val and bar-val
being non-nil, but not directly conditional on
num, which moreover can never be nil.
if-let*、when-let*、and-let*で共通する構造をcondの場合まで拡張するのは容易ではないため、cond-let*マクロは存在しません。10 ただし、cond*マクロのbind-and*節(cond*マクロを参照)を使用することで、同様の処理を実現できます:
(cond* ((bind-and* (result1 (do-computation))
(result2 (do-more result1)))
(do-something result2))
...)
いずれかのバインディングが nil と評価されるまでループを実行する同様のマクロが存在します:
spec内の各バインディングを順に評価し、バインディング値がnilであれば終了する。すべてがnil以外であればthen-formsを実行し、その後ループを繰り返す。なお、ループが繰り返される際、spec内のvalue-formsが再評価され、バインディングが新たに設定される点に注意すること。
varlist は if-let*
と同じ形式です。varlist の各要素は (symbol value-form)
の形式をとり、その中で value-form が評価され、symbol がその結果にローカルに束縛されます。束縛は let* のように順次行われます(ローカル変数 を参照)。value-form のテスト結果のみに関心がある場合は、symbol
に _ を使用してください。すると value-form が評価され、nil
に対してチェックされますが、その値は束縛されません。特殊なケースとして、varlist
のエントリが単に symbol
の形式をとることがあり、これは nil に対してシンボルの現在の束縛を確認することを意味します。
while-let の戻り値は常に nil
です。