JavaLisp Implementation Notes

慶應義塾大学大学院 理工学研究科 計算機科学専攻

近藤 豪

National Institute of Advanced Industrial Science and Technology (AIST) This page is a page of the former research institute. We stopped updating on March 31.2001.
E-mail to webmaster (Japanese) E-mail to webmaster (English)

この文書はJavaLispの設計及び実装に関するものです.

概略


組み込みのデータ型

クラス階層

JavaLisp組み込みのデータ型は,全てクラスで表されその階層を示すと以下の ようになる.
java.lang.Object
        java.lang.Number
                java.lang.Integer
                java.lang.Float
        java.lang.String
        eus.EusObject
                eus.Cons
                eus.Vector
                        eus.IntegerVector
                        eus.FloatVector
                eus.CompiledCode
                        eus.Closure
                eus.PropertiedObject
                        eus.Package
                        eus.Symbol
                                eus.T
                                eus.Nil
                                eus.Lambda
                                eus.Macro
                                eus.SpecialForm
                                eus.Function
                                        eus.Function0
                                        eus.Function1
                                        eus.Function2
                                        eus.Function3
                                        eus.Function4
                                        eus.FunctionN
                        eus.MetaClass
                                eus.VectorClass
        eus.Instance
                eus.VectorInstance
( eus.Macro, eus.SpecialForm, eus.Function は, eusCompiledCodeのサブクラスとして,そのインスタンスをSymbolインスタン スが持つ様にすべきであった.次のべージョンで変更予定.)

インタフェース

Javaでは多重継承が許されていないが,多重継承を利用したい場合がある.例 えば,Nilクラスは,Listでもあり,Symbolでもあるなど.そのような多重継 承の利用が,インタフェースで実現できた.以下にインタフェースとそれを実 装するクラスをあげる.
eus.List
eus.Cons, eus.Nil が実装.
eus.Length
eus.Cons, eus.Nil, eus.Vector, eus.VectorInstance な どのSequenceが,このインタフェースを実装.
eus.Selector
関数 send で送られるキーワードシンボル (eus.nomethodなど)はこれを実装.

read-eval-print

JavaLispにおいて,トップレベルのread-eval-printループは, eus.Toplevelのメソッドread_eval_print_loopが 行なっている.このメソッドでは,ループ内で発生した2種類のExceptionを 受けとるようになっている.一つは,eus.EOFExceptionであり, これは,入力が終了したときに投げられ,これを受けとると正常に終了する. もう一つは,eus.LispRuntimeExceptionであり,これはLispプ ログラムの実行中にエラーが発生した場合に投げられる. 以下で,read,eval,printの実現方法を述べる.

read

Readerは,eus.Reader インスタンスが行なっている.このクラ スのコンストラクタは,引数として入力ストリーム (java.io.InputStream)と,(eus.Package)をとる. 第1引数の入力ストリームは,対話モードの場合,標準入力 (System.in)が与えられ,-eオプションによるバッ チモードのときは,mainメソッドで作られたオプションに続くプログラムを含 むストリーム(java.io.ByteInputStream)が与えられる.第2引 数には,リーダが読み込んだSymbolをinternするパッケージを与える.リーダ は,構文的なエラーが発生した場合には,eus.ParseException を投げる.

文字列をリスト構造に変換する実際のreadはreadメソッドが行 なっている.また,その他のprivateメソッドは,readの下請け メソッドとなっている.

なお,バッククォートの処理は,リーダが変換することによって行なった.

eval

Readerがオブジェクトを返した後は,それを評価するが,そのオブジェクトの 種類には以下のようなものがある. このうち,下の4種類は,評価するとそれ自身となる.よって,JavaLispイン タプリタでは,オブジェクトを評価する場合,どのクラスのインスタンスかを 調べて eus.EusObject の場合のみ評価する,という実装方法と なっている.

他の言語,例えばLispでLispインタプリタを書く場合は,関数evalを作成し, それが重要な位置を占めるが,オブジェクト指向言語でLispインタプリタを書 く場合は,その変わりに各オブジェクト毎にevalメソッドを持たせる,という 設計が考えられる.そして,JavaLispでは,その方針で実装している.

eus.EusObject には,抽象メソッドeval が定義 されているので,それを評価するにはそのメソッドを呼べばいい. eval は,一般変数を束縛する環境と,スペシャル変数を束縛す る環境の2つを引数にとる.ここで,重要と思われるクラスの eval メソッドについて述べたいと思う.

Symbolクラスのevalメソッド

シンボルの評価は,まず環境からスペシャル変数であるかどうかを調べ,そう ならスペシャル変数環境からそのスペシャル値を取り出す.そうでないなら, 一般変数の環境から値を取り出す.

Consクラスのevalメソッド

これが,最もコーディング量が多く,呼ばれる回数も最も多いであろう.この メソッドは,場合に応じて以下のように実装されている.
スロットアクセス (object . slot)
まずcarを評価.それがもしeus.Instanceまたは eus.EusObjectならば,cdrを引数に与えて, getslotメソッドを呼ぶ.

carがスペシャルフォームのとき (specialform args)
スペシャルフォーム car の executeメソッドを呼ぶ.引数 には自分自身と,環境を与える.

carが組み込み関数のとき
抽象クラスeus.Functionのサブクラスには,引数の数0, 1, 2, 3, 4, N (可変個または5つ以上)に応じて,それぞれ,抽象クラス Function0, Function1, Function2, Function3, Function4, FunctionN が定義されている.ここで,Functionkは,抽 象メソッドapplyKを持っている.そして,k 以外のjに対して,メソッドapplyjが起動さ れるとエラーとなり,IllegalFormExceptionが投げられる.

この設計の利点は,引数の数が4以下の固定個の場合evlisによ るオーバヘッドを削減できることにある.また, applykの側でも引数へのアクセスが容易になる.こ の設計はKawa で も採用されている.

k個の引数を持つ組み込み関数はFunction kを継承し,メソッドapplykを持ったクラス として定義される.

よってcarが組み込み関数の場合は,引数のリスト cdrの長さkに応じたapplykを 呼べばいい.引数には,cdrのそれぞれの要素を評価した結果と,環境を与え る.引数の個数が違う場合はエラーとなる.

carが組み込みマクロのとき(macro args)
組み込みのマクロは抽象クラスeus.Macroのを継承して定義 されている.そして,マクロ展開を行なうメソッドexpandが定 義されている.

よって,cdrと環境を引数に与えて car の expand メソッドを起動する.そして,返ってきた値を評価すればいい.

carがユーザ定義関数/マクロのとき (user-defined args)
この場合は,car部のシンボルからラムダ式を取りだし,そこ から eus.Closure を作る.関数のときは,apply を呼ぶ.このとき引数として環境とevlisしたcdr部を渡す.マ クロのときは環境と評価しないままのcdr部を引数として applyメソッドを呼ぶ.そしてそこから返ってきたオブジェクト を評価する.

carがラムダ式のとき ((lambda(...)...) args)
eus.Closure を作り,apply を呼ぶ.このと き引数として環境とevlisしたcdr部を渡す.

その他の場合
エラーであるので,eus.IllegalFormExceptionを投げる.

環境

環境は,eus.Environment で定義されている.一般に環境を新 しく作るには,引数に親の環境を与えてコンストラクタをよべばいい.全く変 数の束縛のない新しい環境を作るには,引数としてnull を与え る (JavaLispは,レキシカル・スコープであるので,関数呼び出しなどの際に はこのように環境を作る).環境から変数の値を取り出すには, get メソッドを用いる.

eus.Environment は,線形リストで eus.Bind を インスタンス変数として持っている.eus.Bind は,変数とその 値のペアである.eus.Environmentextent メ ソッドを呼ぶと,変数とその値の束縛が環境に加わる.

スペシャル変数の実現には,eus.Bind にflagを持たせることに よって実現した.ある変数がスペシャルであることが宣言されると,一般変数 の環境中のその変数を束縛しているeus.Bindインスタンスの isSpecialflag が,trueとなる.それによってその変数がスペ シャル変数であることがわかるので,スペシャル変数の環境を参照するように する.

局所関数も,通常の値と同様に環境に束縛されている.flet で 定義された局所関数は,setLocalFunciton, getLocalFunction で追加,参照ができる.label も同様に,setLabel, getLabel で追加,参照ができる.

print

JavaのオブジェクトはtoStringというメソッドがあるので,そ れによって print を行なう.eus.EusObjectに対しても適切な toStringを定義した.

文字列(java.lang.String)は,Lispインタプリタのprintと,toString が違うので,printを行なう前にインスタンスをチェックして文字列の 場合は,ダブルクォートを前後に付けるようにした.


オブジェクト指向プログラミングの設計と実装

クラスとインスタンスの表現

JavaLispはJavaを用いて記述されているためEusLispとクラスの表現が異なる. よってmetaclassやインスタンスの表現がEusLispとは,大幅に異なっている. 設計で困難だった部分が,実行時に定義されるクラスをどのように表現するか である.consやvectorなどの組み込みクラスの場合は,Javaによってクラスを 定義でき,そのインスタンス生成は,コンストラクタを呼ぶことで簡単にでき るが,インタプリタで動的にdefclassが行なわれると classname.classファイルによってクラス定義をする のは難しい.defclassされたものは,metaclassを作ることになる.しかし, そのインスタンス生成は,非常に込み入ったものになってしまう.metaclass はスロット数やスーパークラスがさまざまである.このユーザ定義クラスのイ ンスタンスを表現するためにクラスを一つ作り,それが eus.Instanceである.そして,そのインスタンス生成は,その コンストラクタにeus.MetaClassを引数として渡してよぶことで 行なう.

この様に,JavaLispでは,組み込みクラスとユーザ定義クラスの間で大きな違 いができてしまった.これは,組み込みクラスを継承するユーザ定義クラスな どの場合は,実装が複雑であるという欠点をもたらす.組み込みクラスもユー ザ定義クラスと同様に扱うという選択肢も考えられ,オブジェクト指向プログ ラミングに関する設計は難しい.

metaclassはmake-classという関数で作る.この関数はマクロ defclassで呼ばれるようになっている.しかし,組み込みクラスに 対するmetaclassは少し複雑で,JavaLisp起動時のクラスロードの際の初期化 で作られる.

メソッド

メソッドは,metaclassが持っておりインスタンスに対してメソッドが呼ばれ るとmetaclassからメソッドを探す.この方法はEusLispと同様である.メソッ ドが見つかると,そのインスタンス自身を環境に加えてclosureを作り,引数 とともに評価する,ということを行なう.

Java Reflection API を用いた インスタンス生成とメソッドの呼び出し

ユーザ定義クラス及び組み込みクラスは,metaclassがクラス定義とメソッド を持っている.しかし,それ以外のクラスであるJavaAPI中のクラスは metaclassがなく,クラスオブジェクト(java.lang.Class)から直 接コンストラクタとメソッドを起動しなければならない.これには,JDK1.1か ら新たに加わったreflection機能を用いる.

make-java-instance

関数make-java-instanceによってJavaAPI中のクラスのインスタ ンスを作ることができる.この関数は,まず第1引数からクラスオブジェクト を得る.java.lang.reflectには,Constructorク ラスが用意されているので,次に,そのクラスオブジェクトから Constructorインスタンスを得て,newInstanceメ ソッドでインスタンスを作る.このとき第2引数以降にコンストラクタに与え る引数があれば与える.

send

sendの第1引数がeus.Instanceでも eus.EusObjectでもなかった場合は,JavaAPI中のインスタンス として,クラスオブジェクトを得る.java.lang.reflectには, Methodクラスが用意されているので,次に,そのクラスオブジェ クトと第2引数から Methodインスタンスを得て, invokeメソッドでメソッドを起動する.このとき第3引数以降に メソッドに与える引数があれば与える.

なお,JavaLispでは,JavaAPI中のクラスを継承してdefclassすることはでき ず,インスタンス生成のみを扱っている.コンパイラによってdefclassから classname.classファイルを定義できるようになれば, クラス定義に関する制限がなくなるだろう.


例外処理のスペシャルフォームの実装方法

Javaでは,例外処理のためのtry/catch構文が用意されており, それを用いてLispの例外処理を実現できる.

例外処理を実現するためにJavaのthrow文が投げるExceptionのクラスをいくつ か定義しなくてはならない.これらクラスは, eus.NonLocalReturnExceptionのサブクラスとして定義した. NonLocalReturnExceptionは,どこからもとらえられずにトップ レベルまで来てしまった場合に出すエラーメッセージをインスタンス変数とし て持つ.

catch/throw

catch/throwを実現するために CatchThrowExceptionを定義した.これは,インスタンス変数と して,catchが識別するタグと,catch文の返り値 を持つ.スペシャルフォームthrowは,第1引数と第2引数で CatchThrowExceptionインスタンスをつくって投げる.一方 catchは,CatchThrowExceptionをcatchするが, まずそのタグを見る.そのタグが一致する場合は CatchThrowExceptionの持つ値を返す.タグが一致しない場合は, catchしたものを,再び投げる.

unwind-protect

unwind-protect文はNonLocalReturnExceptionを catchする.そして,そのまま投げる.ただしJavaには,便利なfinally構文が 用意されている.よって,finallyブロックの中で,第2引数以降の cleanup-formを実行する.

block/return-from

block/return-fromを実現するために ReturnFromExceptionを定義した.これは,インスタンス変数と して,ブロック名と,block文の返り値を持つ.スペシャルフォー ムreturn-fromは,第1引数と第2引数で ReturnFromExceptionインスタンスをつくって投げる.一方 blockは,ReturnFromExceptionをcatchするが, まずそのブロック名を見る.そのブロック名が一致する場合は ReturnFromExceptionの持つ値を返す.ブロック名が一致しない 場合は,catchしたものを,再び投げる.

go/tagbody

go/tagbodyを実現するためにGoTargetException を定義した.これは,インスタンス変数として,タグを持つ.スペシャルフォー ムgoは,第1引数で示されたたぐで ReturnFromExceptionインスタンスをつくって投げる.一方 tagbodyは,タグとそのタグが示す実行文を持っている. GoTargetExceptionをcatchするが,まずそのタグを見る.その タグが自分の所にある場合はそのタグが示す実行文に制御を移す.ない場合は, そのまま投げる.

ファイルの構成

JavaLisp.java
この main メソッドを持ったクラスを実行することによっ てJavaLispインタプリタを実行することができる.
common.l
Javaでなくlispで記述されたCommon Lispの関数,マクロ定義.インタプ リタ起動直後に load される.
constants.l
Javaでなくlispで記述された定数の定義.主にdefsetfを行 なっている.common.lload 直後に load される.

eus パッケージ

このパッケージ中のクラスは,JavaLisp組み込みの基本クラス,リーダ,エラー や例外処理で投げられる例外,そして,他のクラスから利用される下請けクラ スである.

eus.specials パッケージ

このパッケージ中のクラスは,スペーシャルフォームを実現する.

eus.standard パッケージ

このパッケージ中のクラスは,標準的な関数,マクロを実現する.

eus.oop パッケージ

このパッケージ中のクラスは,オブジェクト指向プログラミングに関する関数, スペシャルフォームを実現する.
近藤 豪 kondo@nak.math.keio.ac.jp