今週もLispを勉強していた。
青柳龍也さん著『UNIX短編シリーズ Emacs Lisp』(クォリティ刊)は一応読み終わった。
120ページぐらいの本を、10日以上掛けて読んでいる。
書いてあるコードを全部試して、不明な点を他の本やWebで調べていたらそうなった。
上の本は、短くするためにずいぶん圧縮していて、書いていることを100%理解しようと思ったら時間が掛かる。
いまは2冊目の山本和彦さん著『リスト遊び』に掛かっている。
やはり薄く、濃く、難しい本だが、上の本よりもずいぶん読みやすい。
下の本の方が内容を絞ってあるからだとも思うが、上の本を読んである程度Lisp力が上がっているのだ。
もう一度上の本を再読するとまた分かることがあるかもしれない。
青柳龍也さん著『UNIX短編シリーズ Emacs Lisp』(クォリティ刊)は一応読み終わった。
120ページぐらいの本を、10日以上掛けて読んでいる。
書いてあるコードを全部試して、不明な点を他の本やWebで調べていたらそうなった。
上の本は、短くするためにずいぶん圧縮していて、書いていることを100%理解しようと思ったら時間が掛かる。
いまは2冊目の山本和彦さん著『リスト遊び』に掛かっている。
やはり薄く、濃く、難しい本だが、上の本よりもずいぶん読みやすい。
下の本の方が内容を絞ってあるからだとも思うが、上の本を読んである程度Lisp力が上がっているのだ。
もう一度上の本を再読するとまた分かることがあるかもしれない。
それにしても、Lispは難しい。
言語自体が難しいところがある。
まず、数値や文字列のような「リストでないデータ」のことをアトム(atom)と言う。
アトムは原子のことで、これ以上分かれないもの、と言うほどの意味である。
【C-j】はそういう文字列ではなく、コントロールJの押下を表す。
atomという関数があって、引数がatomだったらt、でなかったらnilを返す。
でもnilを返す場合も対照に見せないと本当のところは分からない。
tはtrueの略で、読み方はティーでいいと思う。
nilは空リスト(からりすと)のことで、()と同値であるが、読み方はニルでいいのだろうか。
ラテン語で「無」のことらしい。
★★★こういうのがむずかしいと感じる。(1回目)
ちなみに、tもnilもatomで評価するとtを返す。
()とも書くが、nilとも書く。
引数がnilだったらtを返す。
でなかったらnilを返す。
(「非Aの世界」と書いて「ナルエーのせかい」と読ませる小説もあった。)
LispはLISt Processorというぐらいで、リストを基本にした言語である。
さて、リストをそのまま評価すると怒られる。
1という不当な関数が現れた、と言われている。
これは、(1 2 3)という式を書くと、1という数字が関数として評価されてしまい、2 3が引数として渡されたことになるからだ。
普通
関数名(引数リスト)
と書くと思うが、
(関数名 引数リスト)
と書くのだ。
★★★こういうのがむずかしいと感じる。(2回目)
で、リストをそのまま評価するにはクォートを書く。
あるものがリストであるかどうかはlistp関数で評価出来る。
リストプだろうか。
listpのpはpredicateのpで、述語という意味だそうだ。
と、言われても良く分からない。
基本的に「~かどうか」という関数のときは、pをつけるようだ。
★★★こういうのがむずかしいと感じる。(2回目)
ところが、atomかどうか判定する関数は、atompではなくてatomなのだ。
なぜか。
つらつら考えたが、listという関数がすでにあるから、という理由のようだ。
引数を使ってリストを作る関数である。
リストかどうかは、listという関数がすでにあるから、listp。
★★★こういうのがむずかしいと感じる。(3回目)
で、nilかどうかを判定する関数は、nilでも、nilpでもなく、nullなのである。
★★★こういうのがむずかしいと感じる。(4回目)
俺だったら、アトムかどうかはatomp、ヌルかどうかはnilp、リストかどうかはlistpにするがどうか。
(pがいいかどうかは良く分からない。checkでcにすればいいかもしれない(atomc、listcなど)が、createとカブるから分かりにくいかもしれない。)
さて、アトムとリストは対立概念であるように思われる。
実際にはそうではない。
nilはatomでもあるし、listでもあるのだ。
★★★こういうのがむずかしいと感じる。(5回目)
まあこれは「無」とは何かみたいな話で、もともと難しい話だからしょうがないところがあるのだろうか。
(consセルとか、コンスセルとか書くこともあるが、これ以降はセルと言う。)
セルは2つのポインタを持つデータで、cons関数で作る。
★★★こういうのがむずかしいと感じる。(5回目)
上に出てきたが、セルは2つのデータをピリオドで挟んで作ることも出来る。
これはcons関数の略記法であり、ドット記法とも言う。
さて、consかどうか判定する関数がconspである。
読み方はコンスピーでいいのだろうか。
後の方をCDRと書いてクダーと読む。
(CD-R(書き込み可能コンパクトディスク)とは何の関係もないので注意。)
あるセルのCAR部を取り出すにはcar関数を、CDR部を取り出すにはcdr関数を使う。
コンテンツ・オブ・デクリメント・パート・オブ・レジスターの略で、CDR。
読み方がクダー。
なるほどねー。
ってわかるか!!
★★★こういうのがむずかしいと感じる。(6回目)
リストは、実はセルの一種である。
CDRにnilが来るセルがリストである。
・nilをCDR部に持つセル
・nilをCDR部に持つセルを、CDR部に持つセル
・nilをCDR部に持つセルを、CDR部に持つセルを、CDR部に持つセル
はリストになる。
そろそろ面白くなってきたと思いませんか。
こういう「数珠つなぎ」がリスト構造、およびLispの妙味である。
上の構造をより理解するには、それぞれのリストのCARとCDRを取ってみるといい。
(1 2 3)のCARは1というアトムである。
(1 2 3)のCDRは(2 3)というリストである。
つまり、CARとCDRは「最初の要素」と、「それ以外全部」という意味である。
逆に、1というアトムと、(2 3)というリストをconsするとリストになる。
(『リスト遊び』によると、Lispの人は「次の会議の最初に、この話題を入れ込んで」という意味で、「次の会議に、この話題をコンスして」と言うそうだ。)
1というアトムと、(1)という一つの要素を持つリストの違いはなんだろうか。
それも、CARとCDRを取ってみると分かる。
先に(1)の方を試してみる。
それはいいんだけど、アトムにcar関数を作用させるとエラーになる。
(cdr 1)も同様である。
(1 2)のcarを取ると1というアトムが返る。
ところが(1 2)のcdrを取ると(2)というリストが返る。
(1)のcarを取ると1というアトムが返る。
(1)のcdrを取るとnilが返る。
つまり、リストは末尾にnilが来るセルである。
それはいいんだけど(ぼくの文章はこのフレーズが多用されているな~)、nilはcarもcdrもnilなのである。
★★★こういうのがむずかしいと感じる。(7回目)
また、CARにnilを持つ(nil . 3)もリストではない。
ところが!
これらのセルlistpに掛けてみる。
★★★こういうのがむずかしいと感じる。(8回目)
(1 2)のような、末尾にnilを持つリストを真リスト(pure list)、(1 . 2)のような末尾にnilおないセルをドットリストと呼ぶそうだ。
名前、意味以外の列見出しは、そういう引数を関数に渡すという意味で、それ以下のセルは戻り値を示す。
個数エラーというのは「Debugger entered--Lisp error: (wrong-number-of-arguments cons 1)」というエラー。
型エラーというのは「Debugger entered--Lisp error: (wrong-type-argument listp 1)」というエラー。
こんな表を覚える必要はなくて、リクツで納得しないとどうしようもないのだが、どうしようもなく意外な値もあって、ある程度覚えないといけない気もする。
ただ、意味もなく覚えるのも消耗である。
何かの役に立ってはじめて、覚え甲斐も出てくる。
お楽しみはこれからだ。
言語自体が難しいところがある。
アトムとatom関数
まず、数値や文字列のような「リストでないデータ」のことをアトム(atom)と言う。
アトムは原子のことで、これ以上分かれないもの、と言うほどの意味である。
9【C-j】上はEmacsのLisp Interaction Modeで、「9」という式をC-j(Ctrl+J)で評価したら、9という値を返した、ということである。
9
"akb48"【C-j】
"akb48"
【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かどうか判定する関数がnullである。
nil
()【C-j】
nil
引数がnilだったらtを返す。
でなかったらnilを返す。
(null 9)【C-j】nullはヌルと読みがちだが、それはドイツ読みで(ドイツで007のことをヌルヌルジーベンと言うらしい)、英語的にはナルだと思う。
nil
(null nil)【C-j】
t
(「非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))
1という不当な関数が現れた、と言われている。
これは、(1 2 3)という式を書くと、1という数字が関数として評価されてしまい、2 3が引数として渡されたことになるからだ。
普通
関数名(引数リスト)
と書くと思うが、
(関数名 引数リスト)
と書くのだ。
★★★こういうのがむずかしいと感じる。(2回目)
で、リストをそのまま評価するにはクォートを書く。
'(1 2 3)【C-j】実際にはクォートはquoteスペシャルフォーム(special form、特殊関数とも)というものの略記法だが、ここでは深く触れない。
(1 2 3)
あるものがリストであるかどうかは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】アトムかどうかは、atom。
(1 2 3)
(listp (list 1 2 3))【C-j】
t
リストかどうかは、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】consはコンスと読み、constructs(構築する)の略である。
(1 . 2)
★★★こういうのがむずかしいと感じる。(5回目)
上に出てきたが、セルは2つのデータをピリオドで挟んで作ることも出来る。
これはcons関数の略記法であり、ドット記法とも言う。
'(1 . 2)【C-j】例によって(1 . 2)とだけ書いて評価すると「不当な関数1」と怒られるので、クォートする。
(1 . 2)
さて、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】CARとCDRの語源については、以下のWeb記事に詳しい。
1
(cdr '(1 . 2))【C-j】
2
Lispのデータ構造コンテンツ・オブ・アドレス・パート・オブ・レジスターの略で、CAR。
最初の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)が使用された.
コンテンツ・オブ・デクリメント・パート・オブ・レジスターの略で、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】だから、consは「リストの最初の要素の前に、別のものをくっつける関数」とも言えるのである。
(1 2 3)
(『リスト遊び』によると、Lispの人は「次の会議の最初に、この話題を入れ込んで」という意味で、「次の会議に、この話題をコンスして」と言うそうだ。)
1というアトムと、(1)という一つの要素を持つリストの違いはなんだろうか。
それも、CARとCDRを取ってみると分かる。
先に(1)の方を試してみる。
(car '(1))【C-j】このように、(1)はCARがアトム1を、CDRがnilを差すセルである。
1
(cdr '(1))【C-j】
nil
それはいいんだけど、アトムにcar関数を作用させるとエラーになる。
(car 1)【C-j】セルやリストを調べるべきcarにアトムを渡したので、「間違った型の引数」というエラーが返された。
(Debugger entered--Lisp error: (wrong-type-argument listp 1))
(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を返すのである。
t
(listp '(nil . 3))【C-j】
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) | nil | 1 2 | '(1 2) | '(1 . 2) | '(1 2 3) |
---|---|---|---|---|---|---|---|---|
atom | アトムかどうか判定する | t | nil | t | 個数エラー | nil | nil | nil |
null | nilかどうか判定する | nil | nil | t | 個数エラー | nil | nil | nil |
list | リストを作る | (1) | ((1)) | (nil) | (1 2) | ((1 2)) | ((1 . 2)) | ((1 2 3)) |
listp | リストかどうか判定する | nil | t | t | 個数エラー | t | t | t |
cons | セルを作る | 個数エラー | 個数エラー | 個数エラー | (1 . 2) | 個数エラー | 個数エラー | 個数エラー |
consp | セルかどうか判定する | nil | t | nil | 個数エラー | t | t | t |
car | CARを返す | 型エラー | 1 | nil | 個数エラー | 1 | 1 | 1 |
cdr | CDRを返す | 型エラー | nil | nil | 個数エラー | 2 | 2 | (2 3) |
名前、意味以外の列見出しは、そういう引数を関数に渡すという意味で、それ以下のセルは戻り値を示す。
個数エラーというのは「Debugger entered--Lisp error: (wrong-number-of-arguments cons 1)」というエラー。
型エラーというのは「Debugger entered--Lisp error: (wrong-type-argument listp 1)」というエラー。
こんな表を覚える必要はなくて、リクツで納得しないとどうしようもないのだが、どうしようもなく意外な値もあって、ある程度覚えないといけない気もする。
ただ、意味もなく覚えるのも消耗である。
何かの役に立ってはじめて、覚え甲斐も出てくる。
お楽しみはこれからだ。