Lispの勉強をしている。
初めに青柳龍也さん著『UNIX短編シリーズ Emacs Lisp』(クォリティ刊)を読了した。
短い本で、駆け足ではあったが、雰囲気を掴むのには役立った。
しかし、あまりにも駆け足なので、ちょっと飲み込めないところはやはりあって、そういうところはもう忘れてしまっている。

次に山本和彦さん著『リスト遊び』も読み終わった。
この本は良書である。
古本がプレミアム価格になっているのも頷ける。
リスト遊び(cons、car、cdr、condなど)に特化した内容で、少しずつ難しい課題を読者に挑戦する形式で、どんどん読み進んでしまった。

今は3冊目の、竹内郁雄さん著「初めての人のためのLISP」に掛かっている。

この本は世評が高く、昔の本がわざわざ再販している、名著の誉れ高い本だが、実用的なプログラムをすぐ学びたい人にはあまり向いていないと思われる。
とにかく冗談が多く、しかも現代の目で見ると笑いどころが分からない。
ぼくも自分の本について余計な冗談が多いと聞かされることが多いが、この本を読んでぼくはまだまだ甘いなと思った。
あと、肝心のLispの知識が、地の文(冗談)にシームレスに入っていて、たりーなーと思いながら油断してナナメ読みしていてフト気づくと、結構難しくて分からなくなっているのだ。
それでも乗りかかった船なので、なんとか読了したい。
あと、他の本を勉強してからまた戻ってくると、すっかりLisp流のユーモアが身について、別の感興を持つかもしれない。

そもそもUbuntuを使っていて、いまいちWindowsのように手に馴染んでいないので、どうせなら手に馴染んでいるEmacsを極限まで使いたいと思ってLispの勉強に入ったので、明らかに遠回りである。
しかし最近はStampVMのようなCommon Lispでカスタマイズするウィンドウマネージャーも出てきているので、ここで学んだことは(どんなことでもそうだが)無意味にはならないと思う。

さて、これまでEmacs Lisp遊びをするときは、ファイルの先頭に
; -*- lisp-interaction -*-1
と書いてLisp Interactionモードにしてから文章(このブログの下書きも含む)を書き、何か実験したいことがあったらLispを書いてC-j(Ctrl+J)を書いて評価してきた。

Lispと文章がシームレスに書けたので、それはそれで便利ではあったのだが、わざわざC-jを押下するのも面倒であった。
これとは他に、「M-x ielm」と実行すると、Emacs Lispのインタープリター環境が現れる。
(M-xはメタエックスと読み、現代のキーボードではAlt+Xまたは、Escを押して、放して、xで入力できる)

これはこれで便利なので、こっちでしばらく実験してみる。
ielm(イエルム?)とは覚えにくい名前であるが、Interactive Emacs Lisp Managerとかだろうか。

さて、Lispには方言がある。
昔は星の数だけ方言があるとか、開発者の数だけ方言があるとか(ということは、Lispで何か開発する人は、まずLispを作っていた?)言われていたが、今はだいたい次の3つに収斂していると思われる。

 ・Common Lisp
 ・Schema
 ・Emacs Lisp

ぼくはEmacsのカスタマイズだけバリバリやりたいと思うのであれば、Emacs Lispさえ勉強すればいいと思うが、基本的な概念を書いている学習書には他の方言のものが多い。
ということで、この3つを平行して、違いを比べたりして楽しみながら学ぶことにした。

いずれもUbuntuであればアプリケーション・センターからラクチンポンで導入でき、しかもEmacsからielm状に実行できる。

Common Lispはいくつか実装があるが、CLispというものをインストールした。
Emacsで使うには~/.emacsや~/.emacs.d/init.elのような設定ファイルに、
(setq inferior-lisp-program "clisp")
と書く。
inferiorというのは「下っ端の」という程の意味だが、Emacs Lispに比べて下っ端ということだろうか。
こっちは以下のページを参考にした。
clmemo@aka: Emacs で快適に Lisp / Scheme ソースを編集する

Schemeもいくつか実装があり、Gaucheをインストールした。
こっちは以下のページを参考にした。
EmacsからGaucheを使う - karetta.jp
Emacsの設定 - karetta.jp

下図はClisp、Gauche、ielmの3つを並べてみたところだ。

Screenshot from 2013-12-11 15:44:54

まず、Common LispとEmacs Lispには空リスト()の略記法nilがあるが、Schemeにはない。
これはSchemeの方が覚えることが少なくて見通しが良さそうだが、そうでなくてもカッコ地獄になるLispの世界で()がそんな重要な意味を持つのはどうだろう。

次に、引数が空リストであるかを判別する述語関数が、Common LispとEmacs Lispではnullだが、Schemeはnull?と言う。
疑問符?がうまく使われていてSchemeの設計思想を良く表している。だったら()?にしてもいいと思うがそれはさすがにややこしいだろうか。

他に、引数がリストであることを判別する述語が、Common LispとEmacs Lispではlistpだが、Schemeはlist?である。
nilかどうか判別するにはnilpではなくてnullを使い、リストかどうか判別するにはnullを使う、というEmacs/Common Lispの不統一さに比べて、()かどうかを判別するのは、null?、リストかどうかを判別するのはlist?というSchemeは美しい気がする。

述語関数が返す真の値はCommon LispはT、Schemeは#t、Emacs Lispはtである。
偽の値はCommon LispはNIL、Schemeは#f、Emacs Lispはnilである。

Clispはなぜか大文字化した出力を表示する。
Schemeの真偽値は#tと#fである。

ここまではまあいいのだが、アトムかどうかを判別する述語は、Common Lisp、Emacs Lispではatomである。
(アトムピーではない)
ところが、Schemeには(少なくともGaucheには)atom?かと思うと、この関数がないのである。
念のためatomもatompも調べたが、ない。
gosh> (atom? 1)
*** ERROR: unbound variable: atom?
Stack Trace:
_______________________________________
gosh> (atom 1)
*** ERROR: unbound variable: atom
Stack Trace:
_______________________________________
gosh> (atomp 1)
*** ERROR: unbound variable: atomp
Stack Trace:
_______________________________________
これは次のようにatom?を定義して置かないといけないということだった。
(define (atom? obj)
(and (not (pair? obj)) (not (null? obj))))
ここまでは何となくCommon LispとEmacs Lispが似た感じで、Schemeが独自路線という気がするが、Emacs Lispが仲間はずれの事象もある。
(cdr '(1 2 3 4 5))
という式はどのLispも(2 3 4 5)になる。
(cdr (cdr '(1 2 3 4 5)))
は(3 4 5)になる。クダーのクダーだからだ。
これをcddrと略記することが出来る。
(cddr '(1 2 3 4 5))
で、これもどのLispも(3 4 5)になる。
クダダーと読みらしい。

cddr - Wiktionary

問題はcdddr、クダーのクダーのクダーであって、クダダダーと読むと思うのだが、Common LispとSchemeは出来るのだがEmacs Lispは出来ない。
*** Eval error *** Symbol's function definition is void: cdddr
というエラーになる。
Common LispもSchemeもクダダダダダーで力尽きる。
[5]> (cdddddr '(1 2 3 4 5 6))
*** - EVAL: undefined function CDDDDDR
gosh> (cdddddr '(1 2 3 4 5 6))
*** ERROR: unbound variable: cdddddr
ここまでをまとめるとこうなる。

ClispGaucheEmacs Lisp
空リストnil、()()nil、()
空リストの判別nullnull?null
リストの判別listplist?listp
T#tt
NIL#fnil
アトムの判別atomなしatom
クダダーcddrcddrcddr
クダダダーcdddrcdddrなし
クダダダダダーなしなしなし

あと、SchemeとEmacs Lispは出来るけどCommon Lispが出来ない現象があれば記事として美しかった気がするが、現状では見出せていない。

さて、クダダーとかそういうのがちょっと面白いのでもう少し研究してみよう。

(caar ...)は (car (car ...))つまりカーのカーである。
ELISP> (caar '((1 2) (3 4)))
1
ELISP> (car (car '((1 2) (3 4))))
1
読み方がちょっと分からない。「カーアー」であろうか。普通の「カー」と微妙である。

(cadr ...)は(car (cdr ...))クダーのカーである。
ELISP> (cadr '(1 2 3))
2
ELISP> (car (cdr '(1 2 3)))
2
読み方はカーダーであろう。

(cdar ...)は(cdr (car ...))カーのクダーである。
ELISP> (cdar '((1 2) 3))
(2)

ELISP> (cdr (car '((1 2) 3)))
(2)
これも読み方が分からない。クダアーであろうか。クダーと区別がつかない気がする。

(cddr ...)は(cdr (cdr ...))つまりクダーのクダーである。
ELISP> (cddr '(1 2 3))
(3)

ELISP> (cdr (cdr '(1 2 3)))
(3)
読み方はクダダーでいいような気がする。

表にまとめるとこうなる。

短縮した関数短縮しないとXが((1 2) (3 4))だと英語の読み方
(caar x)(car (car x))1不明(カーアー?)
(cadr x)(car (cdr x))(2)カーダー
(cdar x)(cdr (car x))3不明(クダアー?)
(cddr x)(cdr (cdr x))(4)クダダー

でも、ふと疑問に思ったけど、ぼくのブログのどの記事のどの部分についても言えることだが、このブログのこのへんとかまだ真剣に読んでる人いるんだろうか。

Emacs Lispは上に書いた2段、4パターンで力尽きるけど、普通のLispであれば4段まで受け付けるそうだ。
1段2パターン、2段4パターン、3段8パターン、4段16パターンだから、合わせて30パターンまで考えられる。
全部書こうかと思ったけど力尽きた。
でもLispで書くとプログラムで生成できそうだなー。
誰か書いてください。

たとえば (caddar x)というのは、読み方はカダダアーとかで良さそうだが、(car (cdr (cdr (car x))))のことだと思う。
調べてみる。
Break 1 [6]> (caddar '((1 2 3 4) 5))
3
Break 1 [6]> (car (cdr (cdr (car '((1 2 3 4) 5)))))
3
ホントダー。
こういうの、Lisp好きも病膏肓に入ると、「そこはクダダダダーじゃないよ!クアダアダダーだよ!」とかそういう会話を宙で出来るようになれるのだろうか。
そうなりたい気もあるが、でも本当はもっと役に立つプログラムが書きたい。
こんなクダーらないことじゃなくて。。(「初めての人のためのLISP」の芸風が伝染ったかも。。)

Franz Liszt c1869 by Franz Seraph Hanfstaengl