「Lispのバトル」
Lisp王座をかけて戦うCommon Lisp対Scheme戦、というようなタイトルは実は読者の注意を引くための嘘だといきなり告白される。ふざけた始まりかたをするが、分かり易くためになるCommon LispとSchemeの比較論だ。
http://symbo1ics.com/blog/?p=7
CLもSchemeも1958年に発明された元祖Lispから枝分れした各種Lispを統一することを目的としている。ただ、その統一方が決定的に違っている。
アプローチの違い
schemeの考え方は規格のなかの一文が表わしている:
「言語は機能を次々と重ねるのではなく、そのような機能を必要と思わせる欠陥と限界を除いていくべきだ」
つまり、Lispのアイデアから拡張可能な「最小限度のもの」を導くことを目的としていた。このアプローチは成功し、50ページの規格が完成した。言語規格としては驚くべき短かさだ。この定義の簡潔さのためコンピュータ科学の勉強には優れた言語となっている。有名な「Structure and Interpretation of Computer Programs」もschemeを使っている。理論や教育だけでなく、実用例も数多くある言語でもある。
CLは全く別のアプローチを選択した。根源的なアイデアを抽出するのでなく、その前のLispたち、特にMacLispから優良な実証された人気の機能を導入した。他のLispとの機能的互換性を重視した。
その結果、大きな言語になった。規格はプリントすると1153ページ。(各関数に例などが入ってだが) 一部の人はCLが互換性のために大変な量のお荷物を背負いこんでいると見る。勉強の対象には向いていないが、目的を達成するためには実用的な言語だ。
シンボルと大文字小文字
Schemeには(事前定義?)シンボルの数が少ない。言語のコアと演算と文字列関数ぐらいだ。CLには沢山ある。これが邪魔になると言う批判もある。
Schemeは大抵、大文字小文字の区別をする。CLは大文字小文字の区別をしないので有名だ。replと対話しているとこれが目立つ。
(囁きに対して叫び返してくる感じがする)
> '(hello how are you today?)
(HELLO HOW ARE YOU TODAY?)
昔はLispは全て大文字で打ち込んでいた時期があった。
関数の名前
CLには古臭い名前が多い。一方、Schemeに自然な名前が多い。(これは英語ネイティブな人でないとピンとこないかもしれない)
Scheme CL
map mapcar
define defun
defparameter
defvar
begin progn
…
CLの風変わりな名前嫌う人がいるが、その名前には理由がある。各種定義はdefで始まる。destructiveな関数はnで始まる(non-consing)。一方schemeはdestructiveな手続に!をつける。predicateにはCLはpをつけ、schemeは?をつける。
関数と値
Scheme: 関数は一級市民。特別扱いはない。
CL: 関数の引数は特別扱いされる
シンボルには「値」と「関数値」の二つがある。(これを実例で解説している。詳しくは原文を)
関数と一般の値のネームスペースを分けたのはコンパイラを書く人ならわかる理由がある。(関数をinlineしやすくなるかららしい)
Schemeはネームスペースが一つなのでLisp-1と呼ばれ、CLはLisp-2と呼ばれる。しかし、本来ならCLはLisp-nと呼ばれるべきだ。dynamic variableなどさらにネームスペースがあるからだ。
マクロの違い
CLのマクロは非常に単純でパワフルだ。コードがevaluatorに辿り着くまでに殆どどんな操作でもできる。(C言語の++nオペレータ同等のものを実装し実例としている)
schemeのdefine-syntaxはシンタックス的なソースコード変換を目的としている。hygeineという特徴があり、不意に変数を捕えることがない。CLではプログラマが回避しなければいけない。(マクロに関してはPaul GrahamのOn Lispが決定版)
二種のマクロの是非に関しては聖戦が繰り広げられたが、何をもって「良い」とするのかを決めないと好みの問題になってしまう。
Schemeのマクロは一度わかってしまうと簡単で柔軟で殆どのニーズに対応できる。現にscheme自体の多くの部分(condやlet)はマクロで実装されている。
CLのマクロは単純で安全なマクロを書く責任をプログラマに割り当てている。CLのマクロによってschemeのマクロを実装できるのでそっちの方が優れているという人がいる。一方schemeのマクロの方が良いとすれば、わざわざそれより原始的なものを持つ必要はないので上の言い分は無効になる。殆どのCLerはSchemeの方が良いとは思っていない。 (各所から元記事著者はCLのマクロの無制限のパワーが好きと伺える)
ライブラリと移植性
どちらも正式な規格があるが、CLのほうがportableだ。規格が小さいものだと実用的な言語を得るためにAliceとBobがそれぞれHash Tableを実装してしまい、これは互換性のないものとなる。では何故ライブラリの規格がないのか? 無いわけじゃない。SRFIというドキュメントがそれにあたる。例えばSRFI-1がリストライブラリでSRFI-13が文字列ライブラリを定めている。しかしこれでもportabilityの問題が克服できない。
一番広く実装されているSchemeの規格はR5RSというものだが、これはライブラリのインポートの仕方を定義していない。R6RSはこの問題を解決したが、Windows Vistaのように不発に終っている。実装毎にまちまちななライブラリ読み込み機能が互換性の無さに寄与している。
CLは最初から実用性を狙っている。ASDFというライブラリのパッケージ方法が広く使われている。最近はQuicklistというものにより、ライブラリの扱いが非常に簡単になっている。そんなわけでCLにおいてはライブラリはportableだ。
実装
「万人と彼等の飼い犬までもが独自のScheme実装を持っている」
実装が多いこと自体は選択肢が増えていいのだが、それは実行スピード、コンパイルスピード、移植性、サイズなどが違えばの話だ。実際には機能が違い互換性がない。さらに、規格に対する準拠もまちまちなのが実情だ。なので、湯気たつschemeの堆積物ができてしまう。(糞のようなschemeがいっぱあるという意味)
例外はChickenとChibi Scheme。… (前者はコミュニティーとパッケージが豊富なScheme->Cコンパイラ。後者は超小型Scheme)
CLは規格が巨大かつ曖昧なので一人は実装できない。なので実装の数がschemeに比べ少ない。だが、十分な数の実装がある。 CLの実装はその多くが規格に準じており、よいコードを生成する。SBCLとCLISPがお勧め。