プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(4)

今度は北海道で大地震ですね。知り合いの家の周りが液状化したらしいです。
とりあえず冬じゃなくてよかった。今週末は非常食を整理して関東大震災に備えます。
今はLispの勉強を進めます。

「Land of Lisp」第4章、条件文の話です。
章タイトルは「Lispは対称なり」です。かっこいい。

Lispの真偽値

Lispでは空のリストは偽として扱われるということから始まります。

CL-USER> (if '() '空括弧はTrue '空括弧はFalse)
空括弧はFALSE
CL-USER>

さらっとif文が登場してきましたが、流します。
空のリストは偽、つまりnilは偽です。
空リストを偽として扱うと、リストの先頭を取ってなにかして、
次の要素をとってなにかして…という処理、再帰がやりやすいそうです。
また、nil以外の値は全て真として扱われるということです。

あ、処理系をslimeにしてみました。この記事もemacsで書いています。
ちゃんとやろうとおもったらやっぱりemacsかなって…。
まだまだ慣れないですが、修行です。
とはいえclispでしか実行できないコマンドが参考書に含まれてるらしいので
またclispで動かしてみることもあると思います。

Lispの条件分岐

Lispの条件分岐を司るコマンドたちが紹介されています。
まずはお馴染み、さっきも出てきたifです。

CL-USER> (if (= 15 (* 3 5))
	     'これは15だ
	     '15じゃないぞ)
これは15だ
CL-USER>

ifについて大切なことは2つあると言います。
①ifの持つ2つの式のうち、実際に評価されるのは1つだけ
②if文でできることは一つだけ

①について、これまで自分はLispは基本的にデータモードで式全体を
評価して、お尻から実行していくように理解していました。
でも、if文はそうではないようです。
本文の中でもされているような実験を実際にしてみます。

CL-USER> (+ 3 (/ 5 0))
; Evaluation aborted on #<DIVISION-BY-ZERO {10046CD323}>.
CL-USER>

コードの中で0除算をしようとすれば当然怒られます。
しかしif文の、実行されなさそうな方に置くと…

CL-USER> (if 't '怒られない (/ 5 0))
怒られない
CL-USER>

何も言われませんでした。
if文はただの関数ではなく、特殊形式であり、
通常のやり方では引数を評価しないからであるそうです。
if文は条件次第である式を評価するかしないか決めるものであるので、
これは当然のことだと書いてあります。ごもっともです。
そして条件コマンドは大体特殊形式だそうです。

②については、if文はどちらか1つしか評価しないので、
片方の分岐で2つ以上の処理をしてもらえないとのこと。
prognというコマンドを使えば、一つの式にいくつも押し込めるらしいです。

CL-USER> (if 't
	     (progn (princ "1つ目")
		    (princ "2つ目"))
	     'どうせこっちにはこない)
1つ目2つ目
"2つ目"
CL-USER>

なるほど、できました。
そして戻り値を見れば分かる通り、
最後の式の評価値をフォーム全体の値として返すようです。

しかし、prognを使わなければ分岐の中で一つしか処理ができないというのは不便です。
そんな気持ちを見透かすかのように、whenとunlessが紹介されます。

CL-USER> (when (= 1 1)
	   (princ "whenは、条件が真のとき、")
	   (princ "渡された式を全て実行します"))
whenは、条件が真のとき、渡された式を全て実行します
"渡された式を全て実行します"
CL-USER> (unless (= 1 0)
	   (princ "unlessは、")
	   (princ "その逆です"))
unlessは、その逆です
"その逆です"
CL-USER> (when (= 1 0)
	   (princ "whenもunlessも、")
	   (princ "真偽値がそれぞれのコマンドに")
	   (princ "合致しなければ何もしません"))
NIL
CL-USER>

これらも最後の式の評価がフォーム全体の値となるようです。
条件が合わなければnilを返してきます。紹介終わり。

そして満を持して、最古にして何でもできるというcondという
万能選手が紹介されます。

CL-USER> (defun study-lang (lang)
	   (cond ((eq lang 'lisp) (princ "lispは神の言語。")
		                  (princ "lispを学んで悟り体験。"))
		 ((eq lang 'c++)  (princ "c++はカッコいい。")
		                  (princ "c++完全に理解したい。"))
		 (t               (princ "なんでも良いよ。。")
				  (princ "ちゃんと使えるなら。"))))
STUDY-LANG
CL-USER> (study-lang 'lisp)
lispは神の言語。lispを学んで悟り体験。
"lispを学んで悟り体験。"
CL-USER> (study-lang 'c++)
c++はカッコいい。c++完全に理解したい。
"c++完全に理解したい。"
CL-USER> (study-lang 'ruby)
なんでも良いよ。。ちゃんと使えるなら。
"ちゃんと使えるなら。"
CL-USER>

ふむふむ。最後にtを置いて、どれにも合致しなかったときの処理を定めるのが
cond使用時の一般的なイディオムらしいです。

最後に紹介されるのはcaseです。上の例をcaseを使って書き直します。

CL-USER> (defun study-lang2 (lang)
	   (case lang
	     ((lisp) (princ "lispは神の言語。")
	             (princ "lispを学んで悟り体験。"))
	     ((c++)  (princ "c++はカッコいい。")
	             (princ "c++完全に理解したい。"))
	     (otherwise (princ "なんでも良いよ。。")
			(princ "ちゃんと使えるなら。"))))
STUDY-LANG2
CL-USER> (study-lang2 'lisp)
lispは神の言語。lispを学んで悟り体験。
"lispを学んで悟り体験。"
CL-USER> (study-lang2 'c++)
c++はカッコいい。c++完全に理解したい。
"c++完全に理解したい。"
CL-USER> (study-lang2 'ruby)
なんでも良いよ。。ちゃんと使えるなら。
"ちゃんと使えるなら。"
CL-USER>

こっちのが読みやすいです。場合によるのだとは思いますが。

また、論理的オペレータとして、andとorがあるそうです。

CL-USER> (and (= 1 1) (= 2 2) (= 3 3))
T
CL-USER> (or (= 2 8) (= 0 0) (/ 8 0))
T
CL-USER> (and (= 0 1) (/ 5 0))
NIL
CL-USER> 

他の言語の&&や||と同じように使えそうです。
orは前から評価していって、どこかで真となればそれ以降は評価しないようです。
andは逆に偽を一つでも見つけたらそこで終わり。
これらを利用して条件分岐処理を書くこともできるようです。
ただ、参考書内でも注意されていますが他人に優しいかどうかは怪しいですね。
ちょいと冗長になってもifを使うほうがよさそうです。

比較関数たち

この章の残りでは、nil以外の全てが真として扱われることを利用した
戻り値に余分に情報を持つ関数の利便性とその欠点、
そして大量の比較関数について説明されます。

戻り値でただの真を返すだけじゃない関数のことは置いておいて、
(置きはしますが結構面白い部分な気がします)
比較関数は…ちょっと多いみたいです。著者のありがたいお言葉を借りると、
・シンボル同士は常にeqで比較すべし。
・それ以外ならequalを使え。
だそうです。
とりあえずこの原則さえ守っていれば、
Lisper達による暴力的な迫害を受ける羽目にはならないだろうとのこと。

大量の比較関数は確かに醜いところだけども、Lisper達がいかに
比較を真剣に考えるかの現れだとフォローされています。
とりあえず著者コンラッドさんの原則に従うとして、
慣れてきたらもっと色々使ってみたいです。

いよいよ次の章からはゲームを作ります。
ここまでみたいにひたすらLispの基本文法をピックアップするのも
そのまま写経するのもつまらないので、
どうに噛み砕いて工夫してアウトプットしたいです。
著作権のことが心配なくなるまで改造できれば最高なんです。

それではまた次回。