condとcase


階層的条件

if 特別式は else 引数を利用することによって階層的な値の検出を行うことができます。 例えば (if test1 then1 (if test2 then2 (if ... という具合に、「〜でなければ、〜でなければ」という形に記述することができます。 こうすれば、階層的にデータを評価することができ、複数の複雑な条件を満たしているかどうかなどを調べることができます。

こうした階層的な分岐はプログラミングでは珍しいものではありません。 C言語のような手続き型言語でも、次のような if 文の階層化は頻繁に発生しています。

if (test1) { ... }
else if (test2) { ... }
else if (test3) { ... }
...

しかし、関数型言語である LISP では、これを頻繁に記述するとコードが読みにくくなってしまいます。 もちろんある程度の量であれば問題ないのですが、数十段階の階層になると致命的です。 そこで、このような階層的な分岐を記述するには cond マクロが適しています。

cond { ( test { form } * ) } *

cond マクロは if 特別式のように、test が nil 以外の値であれば form を評価するというものです。 form には任意の数の式を記述することができ、cond の返す値は最後に評価した form の式となります。 この部分は progn と同じです。

cond の特徴は、この test と form を任意の数だけ記述することができるという部分です。 nil 以外の条件が検出されるまで、cond は指定された式を次々と評価します。 通常、cond マクロは次のように使用します。

(cond	(条件1 式...)
	(条件2 式...)
	(条件3 式...)
	(条件4 式...)
	...
)

このように、任意の数の条件を一つの cond マクロの中に記述できるので、階層的な if 分岐には適しています。 cond は条件1から順番に評価し、条件が nil でなければ式を評価して処理を終了させます。 条件が nil であれば、式を評価せずに次の条件に進みます。

> (setq x 2)
> (cond ((= x 1) 'TukuyomiMode) ((= x 2) 'NekomimiMode))
NEKOMIMIMODE
> (setq x 1)
> (cond ((= x 1) 'TukuyomiMode) ((= x 2) 'NekomimiMode))
TUKUYOMIMODE
> (setq x 0)
> (cond ((= x 1) 'TukuyomiMode) ((= x 2) 'NekomimiMode))
NIL

この LISP コマンドは、変数 x の値を階層的に調べ、x が 1 ならば…、x が 2 ならば…、というプログラムになっています。 例えば x が 1 のときは、記号型 TukuyomiMode が返されて処理を終了しています。 このとき、それ以降の条件は評価されません。 また、全ての条件が nil である場合、cond の値は nil となります。


単一の値を階層的に調べる

cond マクロは if 特別式を用いて階層的な判断を必要とするときに利用できる便利なマクロでした。 一般的な手続き型言語における if-elsif の階層を表現することができます。

これに対して、単一の値を対象に複数の結果に分岐させる場合は cond マクロによる if-elsif 型よりも case マクロの方が便利です。 case マクロは、C系の手続き型言語における switch 文に該当するマクロで、キー式と呼ばれる値とリストを比較して実行する式を決定します。

case keyform {({({ key }*) | key } { form }*)}*

case マクロでは、まず keyform にキーとなる値を指定します。 このキーと、その後に指定する任意の数のリスト key と比較され、key と keyform が等しい場合に form が実行されるという仕組みです。

key は単一の値かリストを指定して複数の値を指定することができます。 form も任意の数の式を指定することができます。 key がリストの場合、リストの値がすべて順番に keyform と比較され、そのうちのいずれかと一致すれば form が実行されます。

> (setq x 1)
> (case x (1 'TukuyomiMode) (2 'NekomimiMode))
TUKUYOMIMODE
> (setq x 2)
> (case x (1 'TukuyomiMode) (2 'NekomimiMode))
NEKOMIMIMODE
> (setq x 0)
> (case x (1 'TukuyomiMode) (2 'NekomimiMode))
NIL

この LISP コマンドでは、変数 x の値によって実行する式を分岐させるものです。 cond では (= x 1) や (= x 2) というように、毎回 x の値を調べていました。 しかし、分岐に作用する値が一つなのであれば case を用いた方がスマートに記述することができます。

> (setq x 1)
> (case x ((1 10) 'TukuyomiMode) ((2 20) 'NekomimiMode))
TUKUYOMIMODE
> (setq x 10)
> (case x ((1 10) 'TukuyomiMode) ((2 20) 'NekomimiMode))
TUKUYOMIMODE
> (setq x 2)
> (case x ((1 10) 'TukuyomiMode) ((2 20) 'NekomimiMode))
NEKOMIMIMODE
> (setq x 20)
> (case x ((1 10) 'TukuyomiMode) ((2 20) 'NekomimiMode))
NEKOMIMIMODE

この LISP コマンドでは、case の key に指定する値をリストで複数個指定しています。 そのため、例えば x が 1 または 10 のいずれかであれば記号型 TukuyomiMode が出力されるという仕組みになります。

case は keyform に指定した値と key の値が一致しなかった場合は nil を返します。 ただし、keyform が指定した全ての値と異なっている場合に式を実行したい場合、最後のリストに t または otherwise 記号を指定します。 これは、C言語の switch で例えると default 句に相当します。

> (setq x 0)
> (case x (1 'TukuyomiMode) (2 'NekomimiMode) (t 'Kiss))
KISS
> (case x (1 'TukuyomiMode) (2 'NekomimiMode) (otherwise 'Kiss))
KISS

注意点として、keyform が指定した記号と等しいかどうかを調べる場合、記号を直接指定するのではなくリストに指定しなければなりません。 例えば、keyform が t や nil であり、t や nil と等しければ実行する式を case に指定したい場合、注意しなければならないミスとして (case keyf (t form) ...) としてしまう間違いがあります。 通常、t や otherwise は case の最後に指定しなければならないので、このマクロはエラーとなってしまいます。

また、(case keyf (nil form) ...) とした場合、keyf が nil であっても form は評価されません。 nil はそのまま評価され (case keyf (( ) form) ...) と解釈されてしまうためです。

> (setq x t)
> (case x (t 'TukuyomiMode) (nil 'NekomimiMode))

*** - CASE: the T clause must be the last one
> (setq x nil)
> (case x (0 'TukuyomiMode) (nil 'NekomimiMode))
NIL

この LISP コマンドを見れば、x が記号 t や nil と等しいかどうかを正しく調べられないことが分かります。 この問題を解決するには、記号型をリストで指定するということに注意しなければなりません。 (t 'TukuyomiMode) は ((t) 'TukuyomiMode) に、(nil 'NekomimiMode) は ((nil) 'NekomimiMode) と記述するのです。

> (setq x t)
> (case x ((t) 'TukuyomiMode) ((nil) 'NekomimiMode))
TUKUYOMIMODE
> (setq x nil)
> (case x ((t) 'TukuyomiMode) ((nil) 'NekomimiMode))
NEKOMIMIMODE

> (setq x t)
> (case x ((quote t) 'TukuyomiMode) ((quote nil) 'NekomimiMode))
TUKUYOMIMODE
> (setq x nil)
> (case x ((quote t) 'TukuyomiMode) ((quote nil) 'NekomimiMode))
NEKOMIMIMODE

> (setq x t)
> (case x ('t 'TukuyomiMode) ('nil 'NekomimiMode))
TUKUYOMIMODE
> (setq x nil)
> (case x ('t 'TukuyomiMode) ('nil 'NekomimiMode))
NEKOMIMIMODE

記号を直接指定すると、記号が評価されてしまうということに注意しなければなりません。 そのため、リストに記号を指定するか、quote や ' によって明示的に記号型の値そのものを与えなければならないのです。



前のページへ戻る次のページへ