★このブログ来週一週間お休みします★

Emacs Lispで遊ぼうの連載。
最初に、Emacs Lisp、Emacs Lispと連呼するのは面倒なので、今後はElispと呼ぶ。

読みかけていた「初めての人のためのLISP」は挫折した。

第7講で「今は使われていないprog」という構文が「もしあったら」という前提で延々プログラム例が出てくるのには少々びっくり。
脳内で実行しなければならないのである。
結構大変だ。

この本を褒めている人は、よく考えたら「他の本でLISPを極めている人が、楽しみのために読み返しているケースが多いのではないか。
ぼくも他の本で勉強したら、またこの本を再読することを期して、とりあえず本書は自宅のスミソニアン博物館に貯蔵しておくことにする。
しかし、ちょこちょこ警句が書いてあって、示唆に富む本ではあると思った。

次に「Scheme手習い(The Little Schemer)」を読み始めた。

本書は、以前読んで感銘を受けた山本和彦さんの「リスト遊び」の「あとがき」で、本書を読んだ後に読むべき本として挙げていた。

さっそく読んでみようと思ったのだが、問題が一つあって、プラットフォームがSchemeなのだ。
ぼくの勉強したいのはElispである。
しかし、ここは両方勉強し、移植してみて、どういう齟齬が起きるか、発見するのも一方だと思った。
SchemeとElispの違いが分かって、いいのではないか。
The Little Elisperだ!

たとえば
ELISP> (atom 'atom)
t
である。Elisp(およびCommon Lisp)はこれでいいのだが、Schemeの場合はatom?という関数を使えと書いている。
ところが、Gaucheでatom?を使ってみると、使えないのである。
gosh> (atom? 'atom)
*** ERROR: unbound variable: atom?
Stack Trace:
_______________________________________
これは実は「はじめに」に書いていることであって、このように定義する。
gosh> (define atom?
(lambda (x)
(and (not (pair? x)) (not (null? x)))))
atom?
実行してみる。
gosh> (atom? 'atom)
#t
出来たー。
前途遼遠である。

'()のcarを取ることは出来ない、と書いている。
しかしElispでは
ELISP> (car '())
nil
となる。Clispでも
[1]> (car nil)
NIL
となる。しかしGaucheでは、果たして
gosh> (car '())
*** ERROR: pair required, but got ()
Stack Trace:
_______________________________________
となった。
(Scheme手習いより)
Carの掟
関数carは空でないリストに対してのみ定義される。
(初めての人のためのLispより)
ワシは少なくともnilのcarがnilになるのはどうも好かん。

実際のプログラムを書くとき、nilのcdrがnilになってくれたほうが、プログラムがちょっと短くなってうれしいときもあるが、nilのcarを取るという行為はそもそもプログラムの論理の誤りであることが圧倒的に多いからじゃ。
また、
Cdrの掟
関数cdrは空でないリストについてのみ定義される。
空でないリストのcdrは常に別のリストとなる。
と書いている。実際Schemeでは
gosh> (cdr '())
*** ERROR: pair required, but got ()
Stack Trace:
_______________________________________
とある。ところがElispでは、nilのcdrも取れて、nilになる。
ELISP> (cdr '())
nil
さて、「Scheme見習い」にはcons関数の第2引数はリストでなければならないとあるが、実際にはアトムでも良い。
アトムの場合は、ドットリストを返す。
ELISP> (cons 'love 'peace)
(love . peace)
gosh> (cons '((a b c)) 'b)
(((a b c)) . b)

これは、訳注にも「実際には出来る」的なことが書いている。

その後に、本文に「Consの掟」というものが書いているが、ということで、実際には例外がある。
Consの掟
関数consは2つの引数を取る。
consの第2引数はリストでなければならない。
結果はリストとなる。
=>実際には第2引数はアトムであっても良い。結果はドットリストとなる。
また、アトムのnullを取ることは出来ないと書いているが、実際には取ることが出来る。
Elispではnil、Gaucheでは#fを返す。
ELISP> (null 'spaghetti)
nil
gosh> (null? 'spaghetti)
#f
ということで
Nullの掟
関数null?はリストに対してのみ定義される。
=>実際にはアトムに対して掛けることも出来る。この場合nilや#fを返す。
これも脚注に書いている。

また、アトムとリストを比較できないと書いているが、実際には、出来る。
まったく同じリストを比較すると真を返す。
違うリストを比較すると偽を返す。
リストとアトムを比較すると偽を返す。
ELISP> (eq '() '(strawberry))
nil
gosh> (eq? '() '(strawberry))
#f
ELISP> (eq (cdr '(soured milk)) 'milk)
nil
gosh> (eq? (cdr '(soured milk)) 'milk)
#f
ELISP>(eq '(love kiss) '(love kiss))
nil

ELISP> (setq lovekiss '(love kiss))
(love kiss)

ELISP> (eq lovekiss lovekiss)
t
gosh> (define lovekiss '(love kiss))
lovekiss

gosh> (eq? lovekiss lovekiss)
#t
ということで
Eq?の掟
関数eq?は2つの引数を取る。
どちらも数でないアトムでなければならない。
=>実際はリストも比較できる。
これも脚注にも書いてある。

まとめると:

「Scheme手習い」には使えると書いているが、実際には定義しなければならない:
・atom?(「はじめに」に書いている)

「Scheme手習い」にはできないと書いているが、実際には出来る(脚注に、実際には出来ると書いている):
・第2引数にアトムを取るcons関数=>ドットリストを返す
・アトムを引数に取るnull?関数=>偽(Elisp、Clispではnil、Schemeでは#f)を返す
・アトムとリストを引数に取るeq?関数=>偽を返す

SchemeとElispの仕様の違いによって、Schemeでは出来ないが、Elisp/Clispでは出来る:
・nilのcar
・nilのcdr
=>両方ともSchemeではエラー、Elisp/Clispではnil

という注意点がある。



このブログの下書きを書いているのはずいぶん前であって、「Scheme手習い」はだいぶ読み進んでいる。
この本は面白い!
ゲーミフィケイションにあふれた、実用的な脳トレのようなものである。
この本のすばらしさを今後おいおい紹介していく。
やさしい本なので、万人におすすめである。

Human devolution scheme