今週もLispを勉強していた。
青柳龍也さん著『UNIX短編シリーズ Emacs Lisp』(クォリティ刊)は一応読み終わった。

120ページぐらいの本を、10日以上掛けて読んでいる。
書いてあるコードを全部試して、不明な点を他の本やWebで調べていたらそうなった。
上の本は、短くするためにずいぶん圧縮していて、書いていることを100%理解しようと思ったら時間が掛かる。

いまは2冊目の山本和彦さん著『リスト遊び』に掛かっている。

やはり薄く、濃く、難しい本だが、上の本よりもずいぶん読みやすい。
下の本の方が内容を絞ってあるからだとも思うが、上の本を読んである程度Lisp力が上がっているのだ。
もう一度上の本を再読するとまた分かることがあるかもしれない。
それにしても、Lispは難しい。
言語自体が難しいところがある。

アトムとatom関数


まず、数値や文字列のような「リストでないデータ」のことをアトム(atom)と言う。

アトムは原子のことで、これ以上分かれないもの、と言うほどの意味である。
9【C-j】
9

"akb48"【C-j】
"akb48"
上はEmacsのLisp Interaction Modeで、「9」という式をC-j(Ctrl+J)で評価したら、9という値を返した、ということである。
【C-j】はそういう文字列ではなく、コントロールJの押下を表す。

atomという関数があって、引数がatomだったらt、でなかったらnilを返す。
(atom 9)【C-j】
t

(atom "akb48")【C-j】
t
ほらね!
でもnilを返す場合も対照に見せないと本当のところは分からない。

tはtrueの略で、読み方はティーでいいと思う。

nilは空リスト(からりすと)のことで、()と同値であるが、読み方はニルでいいのだろうか。
ラテン語で「無」のことらしい。
★★★こういうのがむずかしいと感じる。(1回目)

ちなみに、tもnilもatomで評価するとtを返す。
(atom t)【C-j】
t

(atom nil)【C-j】
t

nilとnull関数

もう出てきたが、要素がゼロ個のリストを空リストと言う。
()とも書くが、nilとも書く。
nil【C-j】
nil

()【C-j】
nil
nilかどうか判定する関数がnullである。
引数がnilだったらtを返す。
でなかったらnilを返す。
(null 9)【C-j】
nil

(null nil)【C-j】
t
nullはヌルと読みがちだが、それはドイツ読みで(ドイツで007のことをヌルヌルジーベンと言うらしい)、英語的にはナルだと思う。
(「非Aの世界」と書いて「ナルエーのせかい」と読ませる小説もあった。)
(null t)【C-j】
nil ;;; tはnilではない

(null (null nil))【C-j】
nil ;;; nilをnullで評価するとtになるが、そのtという戻り値をnullで評価するとnilになる

リスト、listp関数、list関数

複数の値をカッコ()で括ったものをリスト(list)と言う。
LispはLISt Processorというぐらいで、リストを基本にした言語である。
さて、リストをそのまま評価すると怒られる。
(1 2 3)【C-j】
(Debugger entered--Lisp error: (invalid-function 1))

Screenshot from 2013-11-27 19:56:08

1という不当な関数が現れた、と言われている。
これは、(1 2 3)という式を書くと、1という数字が関数として評価されてしまい、2 3が引数として渡されたことになるからだ。

普通
 関数名(引数リスト)
と書くと思うが、
 (関数名 引数リスト)
と書くのだ。
★★★こういうのがむずかしいと感じる。(2回目)

で、リストをそのまま評価するにはクォートを書く。
'(1 2 3)【C-j】
(1 2 3)
実際にはクォートはquoteスペシャルフォーム(special form、特殊関数とも)というものの略記法だが、ここでは深く触れない。

あるものがリストであるかどうかはlistp関数で評価出来る。
(listp '(1 2 3))【C-j】
t ;;; (1 2 3)はリスト

(listp 9)【C-j】
nil ;;; 9はリストではない

(listp t)【C-j】
nil ;;; tはリストではない
読み方はリストピーでいいのだろうか。
リストプだろうか。

listpのpはpredicateのpで、述語という意味だそうだ。
と、言われても良く分からない。
基本的に「~かどうか」という関数のときは、pをつけるようだ。
★★★こういうのがむずかしいと感じる。(2回目)

ところが、atomかどうか判定する関数は、atompではなくてatomなのだ。
なぜか。
つらつら考えたが、listという関数がすでにあるから、という理由のようだ。
引数を使ってリストを作る関数である。
(list 1 2 3)【C-j】
(1 2 3)

(listp (list 1 2 3))【C-j】
t
アトムかどうかは、atom。
リストかどうかは、listという関数がすでにあるから、listp。
★★★こういうのがむずかしいと感じる。(3回目)

で、nilかどうかを判定する関数は、nilでも、nilpでもなく、nullなのである。
★★★こういうのがむずかしいと感じる。(4回目)

俺だったら、アトムかどうかはatomp、ヌルかどうかはnilp、リストかどうかはlistpにするがどうか。
(pがいいかどうかは良く分からない。checkでcにすればいいかもしれない(atomc、listcなど)が、createとカブるから分かりにくいかもしれない。)

さて、アトムとリストは対立概念であるように思われる。
実際にはそうではない。
nilはatomでもあるし、listでもあるのだ。
(atom nil)【C-j】
t

(listp nil)【C-j】
t

★★★こういうのがむずかしいと感じる。(5回目)
まあこれは「無」とは何かみたいな話で、もともと難しい話だからしょうがないところがあるのだろうか。

セル、cons関数、consp関数

さて、セルというデータ構造もある。
(consセルとか、コンスセルとか書くこともあるが、これ以降はセルと言う。)
セルは2つのポインタを持つデータで、cons関数で作る。
(cons 1 2)【C-j】
(1 . 2)
consはコンスと読み、constructs(構築する)の略である。
★★★こういうのがむずかしいと感じる。(5回目)
上に出てきたが、セルは2つのデータをピリオドで挟んで作ることも出来る。
これはcons関数の略記法であり、ドット記法とも言う。
'(1 . 2)【C-j】
(1 . 2)
例によって(1 . 2)とだけ書いて評価すると「不当な関数1」と怒られるので、クォートする。

さて、consかどうか判定する関数がconspである。
読み方はコンスピーでいいのだろうか。
(consp (cons 1 2))【C-j】
t

(consp '(1 . 2))【C-j】
t

(consp 9)【C-j】
nil ;;; 9はセルではない

CARとCDR

セルは、二つのデータへのポインタを持つデータ構造だが、この前の方をCARと書いて、カーと読む。
後の方をCDRと書いてクダーと読む。
(CD-R(書き込み可能コンパクトディスク)とは何の関係もないので注意。)

あるセルのCAR部を取り出すにはcar関数を、CDR部を取り出すにはcdr関数を使う。
(car '(1 . 2))【C-j】
1

(cdr '(1 . 2))【C-j】
2
CARとCDRの語源については、以下のWeb記事に詳しい。
Lispのデータ構造
最初のLisp処理系はIBM 704上で実装された. IBM 704の1ワードは36ビットで, 3ビットのprefix部,15ビットのdecrement部, 3ビットのtag部,15ビットのaddress部から構成されていた. Lispのconsセルが1ワードで表現され, address部にcar部分が,decrement部にcdr部分が格納されたため, それぞれを取り出す命令として CAR (Contents of Address part of Register), CDR (Contents of Decrement part of Register)が使用された.
コンテンツ・オブ・アドレス・パート・オブ・レジスターの略で、CAR。
コンテンツ・オブ・デクリメント・パート・オブ・レジスターの略で、CDR。
読み方がクダー。
なるほどねー。
ってわかるか!!
★★★こういうのがむずかしいと感じる。(6回目)

リストとセル

それはいいんだけど、リストもセルなのである。
(consp '(1 2 3))【C-j】
t
これはどういうことだろうか。

リストは、実はセルの一種である。
CDRにnilが来るセルがリストである。
(cons 1 nil)【C-j】
(1)

(cons 1 (cons 2 nil))【C-j】
(1 2)

(cons 1 (cons 2 (cons 3 nil)))【C-j】
(1 2 3)

'(1 . nil)【C-j】
(1)

'(1 . (2 . nil))【C-j】
(1 2)

'(1 . (2 . (3 . nil)))【C-j】
(1 2 3)
このように、
 ・nilをCDR部に持つセル
 ・nilをCDR部に持つセルを、CDR部に持つセル
 ・nilをCDR部に持つセルを、CDR部に持つセルを、CDR部に持つセル
はリストになる。

そろそろ面白くなってきたと思いませんか。
こういう「数珠つなぎ」がリスト構造、およびLispの妙味である。

上の構造をより理解するには、それぞれのリストのCARとCDRを取ってみるといい。
(car '(1))【C-j】
1
(listp (car '(1)))【C-j】
nil ;;; (1)のCARはリストではない

(cdr '(1))【C-j】
nil
(listp (cdr '(1)))【C-j】
t

(car '(1 2))【C-j】
1
(listp (car '(1 2)))【C-j】
nil ;;; (1 2)のCARはリストではない。1は1というアトムである。

(cdr '(1 2))【C-j】
(2) ;;; (1 2)のCDRは(2)というただ一つの要素を持つリストである

(car '(1 2 3))【C-j】
1
(listp (car '(1 2 3)))【C-j】
nil ;;; (1 2)のCARはリストではない

(cdr '(1 2 3))【C-j】
(2 3)
(listp (cdr '(1 2 3)))【C-j】
t ;;; (1 2 3)のCDRは(2 3)という2つの要素を持つリストである

(1 2 3)のCARは1というアトムである。
(1 2 3)のCDRは(2 3)というリストである。
つまり、CARとCDRは「最初の要素」と、「それ以外全部」という意味である。

逆に、1というアトムと、(2 3)というリストをconsするとリストになる。
(cons 1 '(2 3))【C-j】
(1 2 3)
だから、consは「リストの最初の要素の前に、別のものをくっつける関数」とも言えるのである。
(『リスト遊び』によると、Lispの人は「次の会議の最初に、この話題を入れ込んで」という意味で、「次の会議に、この話題をコンスして」と言うそうだ。)

1というアトムと、(1)という一つの要素を持つリストの違いはなんだろうか。

それも、CARとCDRを取ってみると分かる。
先に(1)の方を試してみる。
(car '(1))【C-j】
1

(cdr '(1))【C-j】
nil
このように、(1)はCARがアトム1を、CDRがnilを差すセルである。

それはいいんだけど、アトムにcar関数を作用させるとエラーになる。
(car 1)【C-j】
(Debugger entered--Lisp error: (wrong-type-argument listp 1))
セルやリストを調べるべきcarにアトムを渡したので、「間違った型の引数」というエラーが返された。
(cdr 1)も同様である。

(1 2)のcarを取ると1というアトムが返る。
ところが(1 2)のcdrを取ると(2)というリストが返る。

(1)のcarを取ると1というアトムが返る。
(1)のcdrを取るとnilが返る。

つまり、リストは末尾にnilが来るセルである。

それはいいんだけど(ぼくの文章はこのフレーズが多用されているな~)、nilはcarもcdrもnilなのである。

(car nil)【C-j】
nil
(cdr nil)【C-j】
nil
まあ、「空リストもリストである」、「空リストの先頭部分は空リストである」、「リストの末尾は空リストを指している」ということであろうか。
★★★こういうのがむずかしいと感じる。(7回目)

セルではないリスト

さて、(2 . 3)というのは、CARが2、CDRが3のセルであって、リストではない。
また、CARにnilを持つ(nil . 3)もリストではない。
ところが!
これらのセルlistpに掛けてみる。
(listp '(2 . 3))【C-j】
t

(listp '(nil . 3))【C-j】
t
どっちもtを返すのである。
★★★こういうのがむずかしいと感じる。(8回目)
(1 2)のような、末尾にnilを持つリストを真リスト(pure list)、(1 . 2)のような末尾にnilおないセルをドットリストと呼ぶそうだ。

リストもリストの要素になれる

'((1 2) 3 4)【C-j】
((1 2) 3 4)

(car '((1 2) 3 4))【C-j】
(1 2)

(cdr '((1 2) 3 4))【C-j】
(3 4)
このように、リスト(やセル)をリスト(やセル)の要素に持つことが出来る。

関数のまとめ

関数ぐらいまとめておく。
名前意味1'(1)nil1 2'(1 2)'(1 . 2)'(1 2 3)
atomアトムかどうか判定するtnilt  個数エラーnilnilnil
nullnilかどうか判定するnilnilt 個数エラーnilnilnil
listリストを作る(1)((1))(nil)(1 2)((1 2))((1 . 2))((1 2 3))
listpリストかどうか判定するnilt t個数エラーttt
consセルを作る個数エラー個数エラー 個数エラー(1 . 2)個数エラー個数エラー個数エラー
conspセルかどうか判定するniltnil個数エラーttt
carCARを返す型エラー1nil個数エラー111
cdrCDRを返す型エラーnilnil個数エラー22(2 3)

名前、意味以外の列見出しは、そういう引数を関数に渡すという意味で、それ以下のセルは戻り値を示す。
個数エラーというのは「Debugger entered--Lisp error: (wrong-number-of-arguments cons 1)」というエラー。
型エラーというのは「Debugger entered--Lisp error: (wrong-type-argument listp 1)」というエラー。
こんな表を覚える必要はなくて、リクツで納得しないとどうしようもないのだが、どうしようもなく意外な値もあって、ある程度覚えないといけない気もする。
ただ、意味もなく覚えるのも消耗である。
何かの役に立ってはじめて、覚え甲斐も出てくる。
お楽しみはこれからだ。