![]() |
|
これは,Lisp処理系は数値などをimediate data(直値)で持つように設計可能 だが,Javaで記述されたLisp処理系では,直値を持つことができないことを意 味する.これは,たくさんの数値演算を行なうプログラムでは実行時間の点で 大きな不利となる.
java.lang.Object を頂点のスーパークラスとするクラス階層を
設計した.その理由は,JavaAPIクラスライブラリをJavaLispで直接利用でき
ないか,と考えたからである.また,整数に,
java.lang.Integer,浮動小数点数に
java.lang.Float,文字列に java.lang.String
を利用することで,コーディングの手間を省くことができる.この設計は,Kawa でも採用され
ている.
また,EusLispと大幅に違うのが組み込みクラスと,ユーザ定義クラスが大き
く違うところである.組み込みのクラスは,Javaのプログラムでかかれていて,
classname.class は実行時にロードされている.よって,
consなどの組み込みのクラスはそのコンストラクタで生成できる.しかし,ユー
ザ定義クラスをインタプリタの実行時にclassname.class
を作って,ロードすることは困難なので,MetaClassをつくり,そこから
eus.Instance クラスのインスタンスをユーザ定義クラスのオブ
ジェクトとして,生成するようにした.
java.lang.Object としてしまったので,実装が汚くなってしまっ
た面もある.例えば,Eval を行なうときには,eval メソッドを
持っているEusObjectに対してはそれを起動し,そうでないときには,例えば
数値のときはそのまま返す,という場合分けをしなければならない.
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インスタン
スが持つ様にすべきであった.次のべージョンで変更予定.)
eus.List
eus.Cons, eus.Nil が実装.
eus.Length
eus.Cons, eus.Nil, eus.Vector, eus.VectorInstance な
どのSequenceが,このインタフェースを実装.
eus.Selector
send で送られるキーワードシンボル
(eus.nomethodなど)はこれを実装.
eus.Toplevelのメソッドread_eval_print_loopが
行なっている.このメソッドでは,ループ内で発生した2種類のExceptionを
受けとるようになっている.一つは,eus.EOFExceptionであり,
これは,入力が終了したときに投げられ,これを受けとると正常に終了する.
もう一つは,eus.LispRuntimeExceptionであり,これはLispプ
ログラムの実行中にエラーが発生した場合に投げられる.
以下で,read,eval,printの実現方法を述べる.
eus.Reader インスタンスが行なっている.このクラ
スのコンストラクタは,引数として入力ストリーム
(java.io.InputStream)と,(eus.Package)をとる.
第1引数の入力ストリームは,対話モードの場合,標準入力
(System.in)が与えられ,-eオプションによるバッ
チモードのときは,mainメソッドで作られたオプションに続くプログラムを含
むストリーム(java.io.ByteInputStream)が与えられる.第2引
数には,リーダが読み込んだSymbolをinternするパッケージを与える.リーダ
は,構文的なエラーが発生した場合には,eus.ParseException
を投げる.
文字列をリスト構造に変換する実際のreadはreadメソッドが行
なっている.また,その他のprivateメソッドは,readの下請け
メソッドとなっている.
なお,バッククォートの処理は,リーダが変換することによって行なった.
eus.EusObject)
eus.Instance)
java.lang.Integer)
java.lang.Float)
java.lang.String)
eus.EusObject の場合のみ評価する,という実装方法と
なっている.
他の言語,例えばLispでLispインタプリタを書く場合は,関数evalを作成し, それが重要な位置を占めるが,オブジェクト指向言語でLispインタプリタを書 く場合は,その変わりに各オブジェクト毎にevalメソッドを持たせる,という 設計が考えられる.そして,JavaLispでは,その方針で実装している.
eus.EusObject には,抽象メソッドeval が定義
されているので,それを評価するにはそのメソッドを呼べばいい.
eval は,一般変数を束縛する環境と,スペシャル変数を束縛す
る環境の2つを引数にとる.ここで,重要と思われるクラスの
eval メソッドについて述べたいと思う.
evalメソッドevalメソッド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.Environment で定義されている.一般に環境を新
しく作るには,引数に親の環境を与えてコンストラクタをよべばいい.全く変
数の束縛のない新しい環境を作るには,引数としてnull を与え
る (JavaLispは,レキシカル・スコープであるので,関数呼び出しなどの際に
はこのように環境を作る).環境から変数の値を取り出すには,
get メソッドを用いる.
eus.Environment は,線形リストで eus.Bind を
インスタンス変数として持っている.eus.Bind は,変数とその
値のペアである.eus.Environment の extent メ
ソッドを呼ぶと,変数とその値の束縛が環境に加わる.
スペシャル変数の実現には,eus.Bind にflagを持たせることに
よって実現した.ある変数がスペシャルであることが宣言されると,一般変数
の環境中のその変数を束縛しているeus.Bindインスタンスの
isSpecialflag が,trueとなる.それによってその変数がスペ
シャル変数であることがわかるので,スペシャル変数の環境を参照するように
する.
局所関数も,通常の値と同様に環境に束縛されている.flet で
定義された局所関数は,setLocalFunciton, getLocalFunction
で追加,参照ができる.label も同様に,setLabel,
getLabel で追加,参照ができる.
toStringというメソッドがあるので,そ
れによって print を行なう.eus.EusObjectに対しても適切な
toStringを定義した.
文字列(java.lang.String)は,Lispインタプリタのprintと,toString
が違うので,printを行なう前にインスタンスをチェックして文字列の
場合は,ダブルクォートを前後に付けるようにした.
.classファイルによってクラス定義をする
のは難しい.defclassされたものは,metaclassを作ることになる.しかし,
そのインスタンス生成は,非常に込み入ったものになってしまう.metaclass
はスロット数やスーパークラスがさまざまである.このユーザ定義クラスのイ
ンスタンスを表現するためにクラスを一つ作り,それが
eus.Instanceである.そして,そのインスタンス生成は,その
コンストラクタにeus.MetaClassを引数として渡してよぶことで
行なう.
この様に,JavaLispでは,組み込みクラスとユーザ定義クラスの間で大きな違 いができてしまった.これは,組み込みクラスを継承するユーザ定義クラスな どの場合は,実装が複雑であるという欠点をもたらす.組み込みクラスもユー ザ定義クラスと同様に扱うという選択肢も考えられ,オブジェクト指向プログ ラミングに関する設計は難しい.
metaclassはmake-classという関数で作る.この関数はマクロ defclassで呼ばれるようになっている.しかし,組み込みクラスに 対するmetaclassは少し複雑で,JavaLisp起動時のクラスロードの際の初期化 で作られる.
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.l の load 直後に
load される.
eus パッケージ
このパッケージ中のクラスは,JavaLisp組み込みの基本クラス,リーダ,エラー
や例外処理で投げられる例外,そして,他のクラスから利用される下請けクラ
スである.
eus.specials パッケージ
このパッケージ中のクラスは,スペーシャルフォームを実現する.
eus.standard パッケージ
このパッケージ中のクラスは,標準的な関数,マクロを実現する.
eus.oop パッケージ
このパッケージ中のクラスは,オブジェクト指向プログラミングに関する関数,
スペシャルフォームを実現する.
近藤 豪
kondo@nak.math.keio.ac.jp