- Common Lisp では、偽は
nilだけで、それ以外は真です。 boolean型はtとnilの2つだけを表します。
一方、条件式ではgeneralized boolean(一般化真偽値、nilが偽でそれ以外が真)を使います。if、when、unlessで真偽による分岐を書けます。andとorは単なる真偽値ではなく、評価した値そのものを返すことがあります。declareでboolean型を宣言すると、「この値はtかnilで返す」という意図を明確にできます。bitは0と1の整数であり、booleanとは別物です。
ビットベクタやビット演算で使います。
1. Boolean の基本
Common Lisp では、条件式において nil だけが偽です。nil 以外の値は、数値の 0 や空文字列 "" を含めて、すべて真として扱われます。
これは generalized boolean(一般化真偽値)という考え方です。
一方で、型としての boolean はもっと狭く、t と nil の2つだけです。
1.1. t と nil
boolean 型の代表的な値は t と nil です。
(list t nil)
;=> (T NIL)Code language: Lisp (lisp)
nil には特別な性質があり、偽であるだけでなく、空リストでもあります。
HyperSpec でも nil は「偽」「空リスト」「空型の名前」など複数の役割を持つと説明されています。
(list nil '())
;=> (NIL NIL)Code language: Lisp (lisp)
2. if / when / unless による分岐
2.1. if で分岐する
条件分岐の基本形は、if です。
if は、条件が真なら第2引数を、偽なら第3引数を評価します。
(defun pass-or-fail (score)
(if (>= score 60)
'pass
'fail))
(pass-or-fail 80)
;=> PASS
(pass-or-fail 50)
;=> FAILCode language: Lisp (lisp)
2.2. cond で条件による場合分け
条件による場合分けでは cond を使います。
(defun grade (score)
(cond ((>= score 90) 'a)
((>= score 80) 'b)
((>= score 70) 'c)
(t 'd)))
(grade 85)
;=> BCode language: Lisp (lisp)
2.3. when で真のときだけ実行する
真のときだけ処理したいなら when を使います。
複数式を書けます。
(defun print-if-positive (x)
(when (> x 0)
(format t "positive: ~a~%" x)
x))
(print-if-positive 3)
; positive: 3
;=> 3Code language: Lisp (lisp)
返す値は、最後に評価した式です。
条件式が nil なら nil を返します。
2.4. unless で偽のときだけ実行する
unless は when の逆で、条件が偽のときだけ本体を実行します。
(defun ensure-name (name)
(unless name
(error "name is required"))
name)Code language: Lisp (lisp)
3. 比較演算と真偽値
3.1. 数値比較
数値比較の結果は、条件式で使える真偽値です。
(defun in-range-p (x lo hi)
(<= lo x hi))
(in-range-p 5 1 10)
;=> TCode language: Lisp (lisp)
3.2. eql / equal / equalp
オブジェクト比較でも真偽値が返ります。
(list (eql 10 10)
(equal '(1 2) '(1 2))
(equalp #(1 2 3) #(1.0 2.0 3.0)))
;=> (T T T)Code language: Lisp (lisp)
equalp は数値型や文字大小の差をゆるく扱うため、広めの一致判定に向きます。
3.3. null で nil を判定する
null は、引数が nil なら真を返します。
nil 判定を明示したいときによく使います。
(defun empty-list-p (x)
(null x))
(empty-list-p nil)
;=> T
(empty-list-p '(1 2 3))
;=> NILCode language: Lisp (lisp)
4. 論理演算の基本
4.1. not で真偽を反転する
not は引数が nil なら t を返し、それ以外なら nil を返します。
CLtL2 でもこの意味で定義されています。
(defun missing-name-p (name)
(not name))
(missing-name-p nil)
;=> T
(missing-name-p "Alice")
;=> NILCode language: Lisp (lisp)
実際には、notは、nullの判定と同じです。
4.2. and で条件をまとめる
and と or は条件付き評価をする制御構造として説明されています。
and は左から順に評価し、途中で nil が出たらそこで止まります。
(defun valid-score-p (x)
(and (integerp x)
(<= 0 x)
(<= x 100)))
(valid-score-p 80)
;=> T
(valid-score-p 120)
;=> NILCode language: Lisp (lisp)
and は常に t を返すわけではありません。
全部真なら最後の値を返します。
(and 10 20 30)
;=> 30
(and 10 nil 30)
;=> NILCode language: Lisp (lisp)
4.3. or で代替値を選ぶ
or は左から順に評価し、最初に真だった値を返します。
すべて偽なら nil です。
(defun user-name (nickname full-name)
(or nickname full-name "guest"))
(user-name nil "Alice")
;=> "Alice"
(user-name nil nil)
;=> "guest"Code language: Lisp (lisp)
こちらも、返り値は必ずしも boolean ではありません。
(or nil nil 42)
;=> 42Code language: Lisp (lisp)
5. 短絡評価
and と or は必要なところまでしか評価しません。
これを短絡評価(short-circuit)といいます。
たとえば、x が 0 のときは、後半の (/ 1 x) まで進まないため、ゼロ除算を避けられます。
(defun safe-reciprocal (x)
(and (not (zerop x))
(/ 1 x)))
(safe-reciprocal 2)
;=> 1/2
(safe-reciprocal 0)
;=> NILCode language: Lisp (lisp)
5.1. and は、前提条件をまとめる
(defun readable-file-p (path)
(and path
(probe-file path)
t))Code language: Lisp (lisp)
最後に t を付けることで、見つかった pathname ではなく boolean を返す形にしています。
5.2. or は、デフォルト値を与える
(defun getenv-or-default (value default)
(or value default))
(getenv-or-default nil "guest")
;=> "guest"Code language: Lisp (lisp)
6. declare と boolean 型
6.1. declare (type boolean ...) の意味
変数や戻り値が boolean 型であることは、declare で明示できます。
ここでいう boolean は t と nil だけです。
(defun even-boolean-p (x)
(declare (type integer x))
(let ((result (evenp x)))
(declare (type boolean result))
result))Code language: Lisp (lisp)
この宣言は、条件式で使える値全般ではなく、本当に t か nil が入る想定で書きます。
6.2. generalized boolean との違い
一方、条件式では「真なら何か値を返す」で十分なことが多く、必ずしも t / nil に正規化する必要はありません。
これを、generalized-boolean 型といいます。
(defun find-user (name table)
(find name table :key #'car :test #'string=))Code language: Lisp (lisp)
この find-user は、見つかれば要素を返し、見つからなければ nil を返します。
条件式にはそのまま使えますが、戻り値は boolean ではありません。
(if (find-user "Alice" '(("Bob" . 1) ("Alice" . 2)))
'found
'missing)
;=> FOUNDCode language: Lisp (lisp)
6.3. 述語関数では t / nil にそろえる慣習
ただ、述語(predicate、真偽を返す関数)として定義する関数は、戻り値を t / nil にそろえるのが一般的です。
このような関数は、慣習的に -p(あるいは-?)という接尾辞を付けます。
(defun non-empty-string-p (x)
(declare (type t x))
(and (stringp x)
(> (length x) 0)
t))
(non-empty-string-p "abc")
;=> T
(non-empty-string-p "")
;=> NILCode language: Lisp (lisp)
途中の and だけだと最後の式の値が返るため、最後に t を置いて boolean に正規化しています。
6.4. boolean と bit の違い
boolean と bit は似ていますが、型としては別です。
bit 型は、0/1 のフラグ列を省メモリで持ちたいときに使います。
bit は 0 と 1 の整数です。bit-vector は、その bit を要素に持つベクタです。
(defun make-flags (n)
(make-array n :element-type 'bit :initial-element 0))
(make-flags 8)
;=> #*00000000Code language: Lisp (lisp)
(defun enable-flag (flags i)
(setf (aref flags i) 1)
flags)
(enable-flag (make-flags 8) 3)
;=> #*00010000Code language: Lisp (lisp)
この 0 / 1 は論理値ではなく整数です。
条件分岐に使う真偽値とは役割が違います。
(list (typep t 'boolean)
(typep nil 'boolean)
(typep 1 'bit)
(typep 0 'bit))
;=> (T T T T)Code language: Lisp (lisp)
しかし、t は bit ではなく、1 も boolean ではありません。
(list (typep t 'bit)
(typep 1 'boolean)
(typep nil 'bit)
(typep 0 'boolean))
;=> (NIL NIL NIL NIL)Code language: Lisp (lisp)
ここで重要なのは、0 は boolean 型ではなく、条件式では真であることです。
(if 0 :true :false)
;=> :TRUE
(typep 0 'boolean)
;=> NILCode language: Lisp (lisp)
7. nil とリストの関係
nil は偽であると同時に空リストでもあります。
言語仕様(HyperSpec)でも nil は空リストとして記法 () と交換可能だと説明されています。
このため、リスト処理と条件分岐が自然につながります。
(defun safe-first (lst)
(if lst
(car lst)
nil))
(safe-first '(10 20 30))
;=> 10
(safe-first nil)
;=> NILCode language: Lisp (lisp)
7.1. consp と組み合わせる
リストが空でない cons かどうかを調べるには consp を使います。
(defun non-empty-list-p (x)
(consp x))
(non-empty-list-p '(1 2))
;=> T
(non-empty-list-p nil)
;=> NILCode language: Lisp (lisp)
consp の返り値は generalized boolean です。
8. まとめ
Common Lisp では、条件式に使う真偽と、型としての boolean を分けて考えるのが大事です。
条件式では nil だけが偽で、それ以外は真です。
これは generalized boolean です。
一方、boolean 型は t と nil だけです。declare (type boolean ...) を使うと、「この値は真偽値として正規化されている」という意図を表しやすくなります。
また、bit は 0 と 1 の整数であり、boolean とは別です。
条件分岐には boolean、大量のフラグ列には bit-vector という使い分けが基本になります。