GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/elisp/Conditionals.html ・ emacs-doc.conao3.com

11.2 条件式

条件制御構造は、複数の選択肢から一つを選びます。Emacs Lispには5つの条件形式があります。他の言語とほぼ同様のif、ifのバリエーションであるwhenとunless、一般化されたcase文であるcond、そしてcondを一般化したpcaseです(パターンマッチング条件を参照)。

特殊形式: if condition then-form else-forms…

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
マクロ: when condition then-forms…

これはifの変種であり、else-formsが存在せず、then-formsが複数存在する場合がある。具体的には、

(when condition a b c)

と完全に等価である

(if condition (progn a b c) nil)
マクロ: unless condition forms…

これは then-form が存在しない if のバリアントです:

(unless condition a b c)

と完全に等価である

(if condition nil
   a b c)
特殊形式: cond clause…

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))

このテーマにはいくつかのバリエーションがあり、以下に説明します。

マクロ: 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 という形式になる場合があり、これは 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 の形式をとることがあり、これは 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 の形式をとることがあり、これは 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 と評価されるまでループを実行する同様のマクロが存在します:

マクロ: while-let spec then-forms...

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 です。


脚注

(10)

問題は、どれも有用ではあるが互換性のないやり方が複数存在することだ。さらに、結果として得られるマクロは複雑で、読み方や書き方を習得するのが非常に難しい。