xyzzy は亀井哲弥氏が作成されたエディタです。このエディタは一見すると Emacs 系と同じですが、このほかに 2 画面型ファイラーを備え、搭載されている Lisp インタプリタ (xyzzy Lisp) は Lisp の標準である Common Lisp に準拠しているなど、数多くの特徴を備えています。
Lisp は方言の多いプログラミング言語で、一時期は実装者の数だけ方言があるといわれました。それを統一するために Common Lisp が発表されたのですが、その基準はあまりにも高すぎました。M.Hiroi は X68000 で動作する Lisp インタプリタ VTOL を作成したときに、Common Lisp の仕様を調べたことがあります。たとえば、数値にしても単純な整数値から多倍長整数、分数、複素数までもが含まれていて、とても個人で処理系を作成できるような仕様ではありません。
けっきょく M.Hiroi が作成した VTOL は、Common Lisp の基本的な仕様を参考にした Lisp の方言にすぎないわけですが、xyzzy Lisp は違います。すべての仕様を満たしてはいませんが、相当に高度な仕様までもが実装されていて、よくぞここまで作ったものだと驚きました。VTOL が勝っているのは、簡単なオブジェクト指向機能が実装されていることぐらいですね。
ところで、xyzzy は優秀なエディタなので、標準のままでも十分に使うことができます。カスタマイズしないのであれば、Lisp を覚える必要もありません。フリーで利用できるプログラミング言語はほかにもたくさんあるし、Lisp なんて時代遅れだと思われる方もいるでしょう。
「Lisp は泥んこ玉だ」と言われることがあります。これは泥んこ玉にいくら泥を追加しても泥んこ玉のままであるように、Lisp にどんな機能を追加しても Lisp のままであるということを表しています。つまり、Lisp には新しい機能を吸収する柔軟性があるのです。Lisp は時代とともに変化し、これからも現役のプログラミング言語として活躍することは間違いありません。
また、Lisp は多くのプログラミング言語に影響を与えています。ほかの言語を学習してみると、Lisp から取り込んだ機能を見かけることがあります。たとえば、Lisp には「無名の関数」や「クロージャ」という機能がありますが、これらは Perl 5 にも取り込まれています。ほかにも、C++の標準ライブラリ STL には Common Lisp でお馴染みの関数 count_if や find_if などが多数用意されています。ファンクションオブジェクトの使い方は Lisp 的だといえるでしょう。このように、Lisp の知識はほかのプログラミング言語を使うときにも役に立つのです。
そして、Lisp をすすめる最大の理由が、プログラミングが簡単でとても面白いからです。少し勉強しただけで、プログラムを作ることができるようになります。Emacs Lisp は仕様が旧式なため少々使いにくいところがありますが、xyzzy Lisp ならば大丈夫です。優秀な処理系が身近にあるのですから、これを機会に Lisp の世界に足を踏み入れてみましょう。
いちばん最初は、C言語で超有名な "Hello, World" を表示するプログラムを作りましょう。 xyzzy は Emacs 系エディタと同じく、起動時から存在する *scratch* バッファで Lisp プログラムを入力し、CTRL キーと j キーを同時に押すと (C-j と記述する)、それを実行してくれます。では、さっそく次のプログラムを実行してください。
(format t "Hello, World\n") <== C-j を入力 Hello, World nil
Hello, World と nil が表示されました。左右のカッコ ( ) やダブルクォート " の数が合わないと、エラーになりますので注意してください。また、カッコの中の format, t, quit は小文字で書いてください。空白は何個書いてもかまいませんが半角文字を使ってください。全角文字はいけません。
たとえば、最後に ) が余分についていると、「一致するカッコが見つかりません」というエラーダイアログが表示されます。簡単なプログラムですが、これでも立派な Lisp プログラムなのです。
それでは、「Hello, World」と表示したプログラムを詳しく説明しましょう。
図 1 : リストの構造 |
---|
|←───── リスト ─────→| | | ( format t "Hello, World\n" ) ~~~~~~ ~ ~~~~~~~~~~~~~~~~ 要素 要素 要素 |
上図のように、左右のカッコ ( ) で括られたものをリスト (list) といいます。カッコは半角でなければいけません。Lisp は LISt Processor の略称ですから、リストがない Lisp は、なにやらのないコーヒーどころの話ではありません。リストは Lisp にはなくてはならないデータ構造なのです。
リストには 2 種類の役割があります。1 つはデータを貯めることです。リストの中に格納されたデータを「要素」といいます。このプログラムのリストの中には、空白で区切られた 3 つの要素 format, t, "Hello, World\n" があります。リストでないこれらの要素をアトム (atom) といいます。
リストは貨物列車にたとえるとわかりやすいでしょう。Lisp では、車両に相当するものをコンスセル (cons cell) といいます。貨物列車には多数の車両が接続されて運行されるように、リストは複数のコンスセルを接続して構成されます。1 つのコンスセルには、貨物(データ)を格納する CAR(カー)という場所と、連結器に相当する CDR(クダー) という場所があります。
図 2 : リスト内部の構造 |
---|
CAR CDR CAR CDR CAR CDR ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │・│・┼─→│・│・┼─→│・│/│ 終端は/で表す └┼┴─┘ └┼┴─┘ └┼┴─┘ ↓ ↓ ↓ format t "Hello, World\n" |
上図では、コンスセルを箱で表しています。左側の CAR がデータを格納する場所で、CDR が次のコンスセルと連結しています。この例では、3 つのコンスセルが接続されています。それから、最後尾のコンスセルの CDR には、リストの終わりを示すデータが格納されます。このデータについてはあとで説明します。
もうひとつは関数 (function) を呼び出す機能です。Lisp はリストが入力されると、第 1 要素を関数名、残りの要素をその関数に与える引数であると判断します。この例では format が関数で、t と "Hello, World\n" が引数となります。
関数は必ず実行結果を返します。最初の例では、nil が関数 format の返り値です。Hello, World は format の働きで画面へ出力されたデータで、関数の返り値ではありません。つまり、format はデータを出力する関数なのです。
違う例を見てみましょう。単純な足し算を考えます。
(+ 1 2 3) => 6
+ は足し算を行う関数です。引数には数値が与えられます。数値データもアトムです。整数のほかにも、xyzzy Lisp では浮動小数点数、多倍長整数、分数などを計算することができます。また、引数の数は 3 つと決まっているわけではなく、+ は複数の引数を受け取ることができます。
それでは、引数に数値以外のデータが与えられたらどうなるのでしょう。次の例を見てください。
(+ (* 1 2) (- 3 4)) => 1
引数がリストになっています。このように、Lisp ではリストの要素にリストを持つことができるのです。これを図に表すと、次のようになります。
図 3 : リストの階層構造 |
---|
┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │・│・┼→│・│・┼→│・│/│ └┼┴─┘ └┼┴─┘ └┼┴─┘ ↓ │ │ + │ │ │ ↓ │ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │ │・│・┼→│・│・┼→│・│/│ │ └┼┴─┘ └┼┴─┘ └┼┴─┘ │ ↓ ↓ ↓ │ − 3 4 ↓ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │・│・┼→│・│・┼→│・│/│ └┼┴─┘ └┼┴─┘ └┼┴─┘ ↓ ↓ ↓ * 1 2 |
このように、リストを入れ子にできることが Lisp の大きな特徴のひとつなのです。
もうひとつ大事なことがあります。それは、Lisp は関数を実行する前に、引数の値をチェックすることです。このとき、引数がリストであれば、そのリストをプログラムとして実行します。関数 + の第 1 引数はリストなので、Lisp はそのリストの第 1 要素を関数として実行しようとします。* は掛け算をする関数です。この結果は 2 になります。
次に第 2 引数を調べます。これもリストなので - を関数として実行します。これは引き算をする関数です。この結果は -1 になります。これで + に与える引数をすべてチェックしたので、最後に + を実行します。そして、その結果が 1 になるわけです。
Lisp は与えられたデータに対し、定められた規則を実行していくことで動作します。このことを評価するといいます。今まで述べたように、リストの場合は、その第 1 要素を関数として扱い、関数を呼び出す前にその引数を評価する、という規則なのです。
数値アトムの場合も規則があります。それは、評価されると自分自身を返す、というものです。(+ 1 2 3) を評価する場合でも、+ を実行する前に引数が評価されますが、数値アトムなのでそのままの値が + に渡されるのです。
最初のプログラムに戻りましょう。
(format t "Hello, World\n")
format が評価される前に、引数が必ず評価されます。第 1 引数の t はシンボル (symbol) というデータです。今まで説明しませんでしたが、format, +, *, - もシンボルです。シンボルには名前が付いているので、ほかのシンボルと区別することができます。
シンボルはデータを格納する変数としての役割と、関数定義を格納する役割を持っています。もうひとつ属性リストというデータを格納できますが、あとで説明することにします。
Lisp はシンボルを評価すると、そこに格納されているデータを返します。ただし、リストの第 1 要素である場合は関数定義を取り出して実行します。format はリストの第 1 要素ですから、関数定義が取り出されて実行されたわけです。では、format にはどのような値が入っているのでしょうか。Lisp にたずねてみましょう。次のようにシンボル名を打ち込めば、そのシンボルに格納されているデータを求めることができます。
format => エラー「変数が定義されていません」
エラーになりました。データをセットする操作をしない限り、シンボルにはデータが入っていないのです。データをセットするには setf または setq という関数を使います。Common Lisp では setf を使うことが推奨されていますが、M.Hiroi は古い人間なので setq を好んで使っています。皆さんは setf を使ってくださいね。
それでは、a というシンボルに数値を代入してみましょう。
a => エラー (setq a 10) => 10 a => 10
a というシンボルに 10 が代入されました。ここで「おかしいな?」と思われたことでしょう。setq が評価される前に、引数であるシンボル a を評価するので、エラーになるはずです。
実は、setq は引数を評価しないで受け取る関数なのです。Common Lisp の場合、引数を評価しないで受け取る関数には、特殊形式 (special form) とマクロ (macro) があります。特殊形式やマクロは、プログラムの制御などを行う場合に使われます。setq は第 1 引数を評価せずにそのまま受け取り、第 2 引数を評価した結果をシンボルに代入します。たとえば、第 2 引数にリストを書けば、その実行結果がシンボルに代入されます。
(setq a (+ 1 2 3)) => 6 a => 6 (setq b a) => 6 b => 6
2 番目の例のように、第 2 引数がシンボルであれば、そこに格納されている値が第 1 引数のシンボルに代入されます。
それでは、t というシンボルはどうでしょう。setq を使って値をセットしていないのでエラーが出そうですが、プログラムは正常に動作しました。ある特定のシンボルには、あからじめデータがセットされています。t というシンボルもそのひとつです。では、値に何が入っているか確認してみましょう。
t => t
自分自身が返ってきました。t というシンボルは条件判断で真を意味するシンボルです。反対に偽を意味するシンボルが nil です。nil の値には、nil が最初から定義されています。実際に真偽を判定する場合、Lisp では nil 以外のデータを真、nil を偽と判定します。
nil にはもうひとつ意味があります。次のように打ち込んでください。
() => nil
( ) は中身の無いリスト、空リストのことです。nil は空リストの意味もあるのです。t や nil は Lisp にとって重要な役割を持つシンボルです。このようなシンボルは値を書き換えることができません。つまり、定数として扱われます。
Lisp > (setq t 10) => エラー「定数は変更できません」
次に第 2 引数 "Hello, World\n" を見ましょう。" で括られたデータを文字列 (string) といいます。文字列もアトムで、評価されるとそれ自身を返します。
"Hello, World\n" => "Hello, World <= 改行される "
\n は改行を意味します。\ をエスケープ文字といいます。\n のほかには \t がタブを表します。
これで format に渡される引数が評価されました。format は第 1 引数が t の場合、標準出力へデータを出力します。第 2 引数には書式を表した文字列を与え、それにしたがってデータを変換します。この場合は、文字列の内容を出力するだけです。
Lisp の変数は、どのようなデータでも格納することができます。ほかのプログラミング言語では、変数に格納するデータの種類をあらかじめ決めておかなければいけません。たとえば、C言語や Java では、変数 a に整数値を格納する場合は int a というように、a に格納するデータの種類を決めます。そして、a には整数値以外のデータを格納することはできません。
Lisp の場合、変数 a には整数値、文字列、シンボル、リストなど Lisp で扱うことができるデータであれば何でも格納することができます。ところで、整数値は setq で変数に代入できましたが、シンボルやリストを変数に代入できるのでしょうか。setq の第 2 引数は評価されることを思い出してください。
(setq x 10) => 10 (setq y x) <== 引数 x が評価され 10 が y に代入される => 10
変数 y にシンボル x を代入する場合、setq にそのまま x を与えると、x が評価されてその値が y に代入されてしまいます。リストの場合は、それがプログラムとして実行されるので、リスト自身を変数に代入することはできません。シンボルやリストを変数に代入するときは、引数が評価されては困るのです。そのため、引数を評価しないようにする関数が用意されています。
(setq y 'x) => x (setq y '(1 2 3 4)) => (1 2 3 4)
引用符 ' を付けると、その次のシンボルやリストは評価されません。引用符は関数 quote の省略形で、'x は (quote x) と同じ働きをします。quote は特殊形式で、引数を評価せずにそのまま返します。したがって、(setq y 'x) の場合、(quote x) が評価されて x 自身が返り値となるので、シンボル x に格納されている値が取り出されるのではなく、x が関数 setq に渡されるのです。その結果、変数 y にシンボル x を代入することができます。
同様に、リストの場合も引用符を付けることで、変数に代入することができます。この場合、リストは評価されない、つまり、プログラムとして実行されないので、最初の要素が関数である必要はありません。'(1 2 3 4) は (quote (1 2 3 4)) に変換され、それが評価されて (1 2 3 4) というリスト自身が関数 setq に渡されるのです。この場合、リストはプログラムではなくデータとして扱われることになります。リストにプログラムとデータというふたつの役割を持たせていることが、ほかの言語とは最も異なる Lisp の特徴なのです。
普通の関数は引数を必ず評価するので、リストやシンボル自身をデータとして扱うために quote 関数を頻繁に使うことになります。(quote (1 2 3 4)) と書いていては面倒だし、プログラムが読みにくくなってしまいます。そこで、'(1 2 3 4) のような省略形が使われるようになりました。
ここで、ここまで出てきた Lisp のデータについて整理しましょう。アトムとリストを合わせてS式またはフォームといいます。データの関係をまとめると次のようになります。
図 4 : Lisp の基本的なデータ |
---|
S式 ─┬─ アトム ─┬─ 整数値 │ │ │ ├─ 文字列 │ │ │ └─ シンボル │ └─ リスト |
今まで使ってきたデータの種類には、リスト、整数値、文字列、シンボルがあります。データの種類を型といいます。このほかにも、配列型データや文字型データなど重要なデータ型がいくつかあります。
Lisp はS式を評価することで動作しますが、その評価規則はデータ型によって決められています。
たとえば、(+ 1 2 3) を評価する場合、関数 + を評価する前に、引数の 1, 2, 3 を評価します。この場合、引数がリストやシンボルではないので、そのまま関数に渡されます。評価しても自分自身になるデータ型を自己評価フォームといいます。Lisp の場合、引数を評価する順番も決まっていて、左から右へ、つまりリストに並んだ順番で評価します。通常の関数では、引数は必ず評価されることを覚えておいて下さい。
ただし、特殊形式やマクロの場合は引数を評価しないことがあります。setq は引数を評価しませんでしたね。通常の関数は引数を評価するが、特殊形式やマクロは違うことに注意して下さい。
以上で Lisp の基本的な動作を説明しました。Lisp では、リストはプログラムとデータのふたつの役割を持っています。この特徴があるため、とても柔軟なプログラミングが可能になるのです。ですが、このままではまだ何もできませんね。Lisp には、あらかじめ用意されている便利な関数がたくさんあります。どのような関数があるか、具体的に見ていくことにしましょう。