上記の時に、筆者が使用していたCommonLispはKyotoCommonLisp(略称KCl)とい
うものです。
KClは当時京都大学におられた湯浅太一先生(豊橋技科大を経て現京大)萩谷昌己
先生(現東大)のお二人が、 CommonLispの仕様書を読んだだけで作られたもので
す。
CommonLispはそれまでのアメリカLisp界の2大潮流であるZetaLispとInterLisp
を統合し、ソフトウェア産業界で使用できる共通のLispを定めようとしてできた
ものです。
ZetaLispもInterLispもその仕様の大きさは有名だったのですが、それらを統合
するにあたり、CommomLispも非常に大きな仕様を持つことになりました。
で、その巨大なLispを東洋の片隅で2人の人間が作ったので、アメリカ人も大変
に驚いたということです。
今回、紹介するGNU Common Lisp(GCL)は実はこのKClがGNUに寄付されて、名前
がGCLに変っただけのものです。
GNUのStallmanと言えば、Lispの本家であるMIT AI Lab. の出身で、Lisp処理
系をバリバリと作ってしまうおじさんなのですが、そのGNUがKClをもらって、
GCLにしたということは、 KClの凄さを何よりも現していると言えるでしょう。
KCl(GCL)は色々とすぐれているのですが、その中でも、もっとも特徴的ですぐ
れている点は、そのほとんどがC言語で書かれている。それに加えて、Lispコン
パイラの生成するオブジェクト・コードがC言語のソースとなっている点です。
CommonLispを作り、移植性を持たせるのは、大変なことです。しかも、性能を
良くするのは非常に大変なことです。
移植性があり、性能のよいコンパイラを作るのも非常に難しいことです。KClは
オブジェクト・コードを C言語とすることで、いとも簡単にその壁を乗り越えて
います。
実は、当時は筆者は「Cのソースを出すなんて単なる手抜きか?」と思っていた
のですが…
その後、SparcやMipsを始めとするRISCの時代になり、C言語コンパイラの最適
化が非常に良くなり、へたなコンパイラではどうしようもない時代になります。
また、CPUの世代交代のサイクルが早くなり、とてもLispコンパイラで機械語を
扱っていられない時代になってきました。
この点、KClのコンパイラは機械に依存する最適化をCコンパイラに任せている
ため、非常によい最適化が行われます。
逆に、例えば、エール大学のT言語(Lispの一種)処理系は機械語を直接に出力す
るコンパイラを持っていますが、それのSparc版などは、いつもほとんど決りきっ
たコードをつなぎ合わせて吐き出すだけで、レジスタの詰合せなどはほとんどやっ
てくれません。また、このT言語処理系は開発が止まっているので、SuperSparc
などのスーパースカラCPUなどには、対応のしようもなく、どうにもなりません。
KClであれば、Sparcなどは、SUNのコンパイラが賢くなったら、それだけで、最
適化が進歩します。
いまでは、CPUのモデルごとに異るキャッシュの量やALUの数などに応じて、個
別に最適化を行わなければならないので、もうC言語コンパイラに頼らなければ、
移植性があり高性能なコンパイラを作ることは難しいでしょう。
この素晴らしいKClがGCLとなって、いますぐFreeBSDで使えます。
ただし、現在、 CommonLispはその規格が第2版に更新され、オブジェクト指向
サポート機構などが仕様に含まれていますが、KCLはCommonLisp第1版準拠なので、
仕様が古くなっています。
KClからGCLとなって、手は加えられているようですが、第2版仕様にまでしては
くれていないようです。
GCLはCommonLisp第1版とはいえ、計算機のパワーが非常に大きい今こそ、仕事
に便利に使えます。
例えば、CommonLispは32bitを越える大きな整数をなんの苦もなく扱えるので、
筆者の回りでは、280 bitも幅のある水平型マイクロコードのアセンブラを
CommonLispで書いたりしています。
GCLはFreeBSDのパッケージにすでに入っているので、すぐにインストールして
使用できます。
ただし、CommonLispを本気で使用する場合はスワップ領域を充分に取っておい
て方がよいでしょう。X WindowとMule(GNU Emacs)も同時に使用するなら、今な
ら、 150Mバイト締度のスワップ領域は用意した方がよいと思われます。(筆者は
スワップが40MバイトしかないノートPCでもこれらを使用しています。しかし、
そのマシンでは大きな仕事はしていません)
インストールには、packages/lang/gcl*を使用します。
筆者はFreeBSD2.2.2を使用しているので、gcl-2.0を使用しました。
# pkg_add gcl-2.0.tgzとします。
早速起動してみましょう。
% rehash % gcl GCL (GNU Common Lisp) Version(2.0) Thu Mar 7 06:44:49 PST 1996 Licensed under GNU Public Library License Contains Enhancements by W. Schelter >(car '(a s d)) A >いい感じですね。
16進数電卓としても使えます。
>(+ #x11 9) 26「#x」は16進数を表す接頭語です。ここでは、0x11(17) + 9を計算して、答が 26と出ています。
>(format t "~x" (+ #x11 9)) 1A NIL
>(format t "~x" (+ #x123456789abcdef00000 1)) 123456789ABCDEF00001 NILで80bitの加算ができています。
GCLは'('に対応するだけの')'が打ち込まれないと、打ち込まれた式の評価(実
行)を始めません。従って、リターン・キーを押下しても、ただ画面上で改行が
起こるだけです。
GCLが何も反応しなくて、おかしいなと思ったら、とりあえず ')'をたくさん入
力して下さい。多すぎる')'は無視されるので問題ありません。
また、実行時エラーが起きると、次のようなブレーク・レベルが起動されます。
(この例では、未定義な関数「asd」を実行しようとして、エラーになった)
>(asd) Error: The function ASD is undefined. Fast links are on: do (use-fast-links nil) for debugging Error signalled by EVAL. Broken at EVAL. Type :H for Help. >>:q Top level. >
作成したプログラムが無限ループなどに入ってしまった場合は、C-c(CTRL-C)を
入力するとブレークして、ブレーク・レベルに入ります。C-cを入力しても、ブ
レークされるまで少しの間があるのですが、それは正常です。
プログラム・ファイルのロード(ここでは、tst.lspというファイルがカレント・ ディレクトリにあるとします)は、
>(load "tst.lsp") Loading tst.lsp Finished loading tst.lsp Tとします。
プログラム・ファイルのコンパイルは、
>(compile-file "tst.lsp") Compiling tst.lsp. End of Pass 1. End of Pass 2. OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3 Finished compiling tst.lsp. #"tst.o"です。
>(load "tst.o") Loading tst.o start address -T 22cb38 Finished loading tst.o 96この様に起動中のプログラムにあとからダイナミックにコードを読み込みリン クすることを「インクリメンタル・ロード(またはリンク)」と呼びます。インタ プリタ言語にはよく実装されています。
GCLは関数単位のコンパイルも行えます。
まず、fa1(階乗関数)という関数を定義してみましょう。
>(defun fa1 (n i) (if (zerop n) i (fa1 (1- n) (* n i)))) FA1このfa1をコンパイルするには
>(compile 'fa1) Compiling gazonk0.lsp. End of Pass 1. ;; Note: Tail-recursive call of FA1 was replaced by iteration. End of Pass 2. OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3 Finished compiling gazonk0.lsp. Loading gazonk0.o start address -T 22df4c Finished loading gazonk0.o #<compiled-function FA1 >とします。
CommonLispにはディスアセンブル機能があります。本来は機械語になったルーチ
ンをディスアセンブルして表示するものです。が、GCL(KCl)ではLispソースをC
言語へコンパイルしたものを表示します。:-) ディスアセンブル可能な関数はコ
ンパイル前のものです。
従って、もう一度、fa1をdefunし直した後、disassembleを行うと、以下のよう
な出力が得られます。
>(disassemble 'fa1) Compiling gazonk0.lsp.End of Pass 1. #include <cmpinclude.h> #include "gazonk0.h" init_gazonk0(){do_init(VV);} /* function definition for FA1 */ static L1() {register object *base=vs_base; register object *sup=base+VM1; VC1 vs_check; {object V1; object V2; V1=(base[0]); V2=(base[1]); vs_top=sup; TTL:; if(!(number_compare(small_fixnum(0),(V1))==0)){ goto T2;} base[2]= (V2); vs_top=(vs_base=base+2)+1; return; T2:; {object V3; V3= one_minus((V1)); V2= number_times((V1),(V2)); V1= (V3);} goto TTL; ;; Note: Tail-recursive call of FA1 was replaced by iteration. } } End of Pass 2. OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3 Finished compiling gazonk0.lsp. #( #((system::%init . #((system::mf (lisp::quote user::fa1) 0) (system::debug (lisp::quote user::fa1) (lisp::quote (user::n user::i)))))) ) static L1(); #define VC1 #define VM1 3 static char * VVi[1]={ #define Cdata VV[0] (char *)(L1) }; #define VV ((object *)VVi) Tこの結果で、注目したいのは、fa1の中からfa1を再帰呼び出ししている部分が、 テイル・リカージョンのオプティマイズにより、goto TTL;に置き換えられてい ることでしょう。
(setq inferior-lisp-program "gcl")としてLisp処理系のコマンド名を設定しておきます。
% mulemuleが起動したら、M-xを打鍵し、ミニバッファで「run-lisp」と打ち込み、リ ターンを入力します。
C-x C-e | 直前のS式を評価 |
C-c C-l | ロード・ファイル |
C-c C-k | コンパイル・ファイル |
M-C-x | カーソルがあるdefun を評価 |
C-c C-e | カーソルがあるdefun を評価 |
C-c C-c | カーソルがあるdefunをコンパイル |
C-c C-r | リージョンを評価 |
C-c C-z | Lispバッファへスイッチ |
C-c C-d | シンボルに関する事柄を表示 |
C-c C-f | 関数のドキュメントを表示 |
C-c C-v | 変数のドキュメントを表示 |