このドキュメントは、「MJ 言語仕様書」に書くべき内容をとりあえず メモにしたもで、 あいまいな点や間違っている点を含んでいる場合があります。 また、先頭から読んで分かるような構成になっていません。
内容はすべて、「 Java 言語に対する違い」という形で説明してあります。 したがって、 Java 言語の知識はあるものと仮定しています。
本当はプログラム例をふんだんに示して説明すべきですが、 まだほとんど書いてありません。
m2 uses m1 の時、 m1 は m2 の super-module 、 m2 は m1 の sub-module と呼ぶ。 (なお、 m2 extends m1, m2 complements m1 は、ともに m2 uses m1 をも意味するものとする。)
Java 言語で書かれたクラスを Java class 、 MJ 言語で書かれたクラスを MJ class と呼ぶ。 (Java class を継承する MJ class を定義できるが、逆は不可。)
Java class が定義したメソッドセレクタを Java method 、 MJ class が定義したメソッドセレクタを MJ method と呼ぶ。 (Java class は Java method しか持ち得ないが、 MJ class は Java method と MJ method の両方を持ち得る。 MJ class は、 super class から Java method を継承するため。)
MJ 言語によって Java method に対し差分を追加した場合も、 それは MJ method と呼ばず、 Java method と呼ぶ。
SS class は startup-singleton class の略。 プログラムの起動に使われる singleton object を定義するクラス。
モジュール間の uses 関係(すなわち継承関係)を有向グラフで表現したもの。
モジュールを topological sort によって1列に並べた結果のリスト。 例えば、 m1 extends m0, m2 extends m0 の時、 m0, m1, m2 の linearized list は (m0 m1 m2) と表記することにする。 super module 側が左に来るように書く。 また、「linearized list 上で、m1, m2 よりも上にある」という表現を用いる。 (右、左、前、後という表現は、誤解を招きやすいので使用しない。)
extends 宣言は、従来の言語の require, import, makefile, patch を兼ねている。 これらの機能を1つの宣言に unify したものが module の extends である。
もっと詳しく説明すると extends 宣言が有する機能は、 他の言語における以下に挙げる機能に相当する。
extends 宣言は、 module を linearize するときの上下関係の制約の宣言も 兼ねている。 そのため、「extends の関係」にサイクルがあると、コンパイル時エラーになる。
しかし、特にプログラムの設計の初期段階では、 module 間の相互依存が許されないと 非常にプログラミングがしにくい。 そこで、用いるのが uses 宣言である。 uses 宣言は extends 宣言と似ているが、 linearize するときの上下関係に制約を加えない。
(省略。)
Java 言語のライブラリを利用するために便利だから存在しているにすぎない。 (もしすべてのライブラリが MJ 言語で書かれれていれば、この機能は不要。)
メソッド名には単純名と完全限定名がある。 単純名は単なる Java の identifier 。例えば foo 。 完全限定名は、モジュール名と単純名の組。例えば FQN[a.b::foo] 。
メソッドセレクタは、
の組である。
MJ のプログラム中で新たなメソッドセレクタを定義するときには、 メソッド宣言に "define" を付ける必要がある。
メソッドセレクタは、メソッド呼び出し処理だけではなく override 処理でも使われる。 クラス継承およびメソッド継承においては、 「同じメソッドセレクタを持つメソッド断片」が override される。
これは、 MJ 以外の言語にはない概念である。 また、 MJ においても、コンパイル時にしか存在しない概念である。
「メソッド拡張点、 extensible point for method」とは、 「既存の部品を拡張するためのソケット」のようなものである。
メソッド拡張点を一意に指定するためには、
の情報が必要である。
ここで、「クラス完全限定名」の情報は、次の理由で必要となる。 クラス C の subclass SubC があり、 C がメソッド m1 を持っているとする。 このシステムに対して差分を追加するとき、 C の m1 を拡張するのか、 SubC の m1 を拡張するのかを、 区別する必要があるためである。
ソースコード中の method body 、すなわち { ... } の部分を、 「メソッド断片、 method fragment 」と名付ける。 あるいは、単に「メソッド」と呼ぶ場合もある。 メソッド断片は、コンパイル時にも実行時にも存在する概念である。 (実行時には、スタックトレースの各行がメソッド断片に対応する。)
メソッド断片を一意に指定するには、
の3つの組の情報が必要である。 この組を「メソッド断片名」と呼ぶ。
・実行時にクラスが持つ「メソッドテーブル」は、 「メソッドセレクタ」から「メソッド断片」を得るための表である。
・ MJ ではメソッドセレクタに「ターゲットの静的な型の型名」の 情報が入っている点に注意。
Car というクラスと Figure というクラスが共に move というメソッドを 持っていたとしても、もしそれぞれが別々に定義されたものであるならば、 2つの move は全く別のものである。
従来のオブジェクト指向言語には、この2つの move を区別しないものもある。 Smalltalk, CLOS は全く区別しない。 Eiffel は、区別する。 C++ は区別するが、多重継承で2つのメソッドが同一になってしまうため、 区別が不完全である。 また、 template と g++ の signature という機能では、区別していない。 Java はクラスが定義するメソッドに関しては多重継承がないので 区別する必要がない。interface が定義するメソッドについては区別していない。 Java の Reflection API でも区別していない。
・ MJ の言語仕様を厳密に説明するには、 「メソッド単純名」「メソッド完全限定名」「メソッドセレクタ」 「メソッド拡張点」「メソッド断片名」の5つの概念を区別する必要がある。
従来のオブジェクト指向言語には、このうち、 「メソッド完全限定名」と「メソッド拡張点」という概念は存在しない。 (「メソッド完全限定名」は多重継承における名前の衝突の問題を解決するために 導入された概念であり、「メソッド拡張点」はプログラムに対する 差分の追加を可能にするために導入された概念である。)
つまり従来のオブジェクト指向言語では 「メソッド名」「メソッドセレクタ」「メソッド(のID)」の3つの概念ですむ。
overload がない言語ならば、「メソッドセレクタ」と「メソッド」の2つですむ。
・ Java においては、 a.b.C#foo(int, String) という書き方で、 メソッド(MJ でいうメソッド断片)を指定することができた。 MJ では、実行時の話ならばこれでもよいが、 ソースコード上の話をするときには不十分である。 MJ でメソッド断片を指定するには、
FQN[m1::C]#FQN[m1::foo](int, String)@m2
という表記を用いることにする。 メソッド完全限定名が必要になる場合はあまりないし、 クラス完全限定名は m1.C と表記できるので、次の簡略記法も用いる。
m1.C#foo(int, String)@m2
・Java におけるメソッドセレクタについて。
言語仕様上は、メソッドセレクタは「メソッド名」と「引数の型」の情報のみで 同定される。 Reflection API もそれにしたがったデザインになっている。
コンパイラ内部では、 public でない同名のメソッドが複数ある場合を 区別するために、名前空間(=ターゲットのクラス名)の情報を つけて区別しているように想像できる。
JavaVM では、「ターゲットのクラス名」に加えて「返値の型名」の情報も加わる。 (ちなみに GJ は、返値の型だけを変えたメソッド定義のコードを生成する。)
・現在の MJ の文法では、実は次のような不都合がある。 次の場合に、 C がどのメソッドを参照しているのかがあいまいになる。
module m { define interface I1 { define void foo(); } define interface I2 { define void foo(); } define class C implements I1, I2 { void FQN[m::foo](){ //あいまい。 ... this.FQN[m::foo]();//あいまい。 } } }
このように、同一モジュール内で定義された同じ名前を多重継承したために衝突する 問題については、今は考慮していない。 この衝突を回避するために、方法は2つある。
1つは、メソッドセレクタを指定するときに、 定義通りに、「ターゲットとなるオブジェクトの静的な型の型名」の情報を 書くようにする方法である。 しかし、この方法は、メソッドセレクタの記述方法が一層繁雑になるという 欠点がある。
2つめは、同一モジュール内の異なるクラスで、 同じ単純名・引数を持つメソッドを「定義」してはいけない、というルールを 入れるという方法。このような制約を入れても、実用上は、多分困らない。
・他のモジュールで「定義」された名前を、モジュール内から「利用」可能にする。 ここで、「利用」とは、以下のことを意味する。
・モジュール同士は相互に uses しあってもかまわない。
ただし、相互依存するモジュールが含まれる全てのソースファイルは、 mjc の引数に同時に渡されなければならない。 逆に言えば、相互依存する複数のモジュールを分割コンパイルすることは不可能。
・ uses していないモジュール中の名前は、 完全限定名を用いても利用することができない。 (ただし Java class のクラス名は、完全限定名で利用できる。)
・ module m3 uses m1, m2 と宣言されていて、 もしリンク時にリンク対象モジュールに m3 が指定されたなら、 m1, m2 も自動的に link される。
・ m2 uses m1, m3 uses m2 ならば m3 uses m1 であるとする。
・ uses 宣言で他のモジュール中の名前をアクセス可能にすることを 「名前空間の継承」と呼ぶ。
・ extends 宣言は、 module を linearize するときの制約条件になる。 例えば module m3 extends m1, m2 と宣言した場合、 linearized list において、 m1, m2 が必ず m3 の上に来る。 (super-module 側を「上」と定義する。)
m1 と m2 の順序関係に関しては、制約はつかない。
・(linearize のアルゴリズムの詳細は省略。 現在実装されているアルゴリズムは、 モジュール名の情報を使って monotonic な linearize を行う。)
・ class, interface, method を「拡張」する時は、 それらの名前が定義されている module を extends 宣言しなければならない。 ここで、「拡張」とは、以下のことを意味する。
( extends しないでこれらの拡張を行った場合はコンパイルエラーになるべきだが、 現在は正しく実装されていない。)
・ m2 extends m1, m3 extends m2 ならば m3 extends m1 であるとする。
・以上述べたことを除けば、 extends は uses と同じ。
・ complements は、補完の対象となるモジュールを指定する。 例えば module m3 complements m1, m2 と宣言した場合、 もしリンク時にリンク対象モジュールに m1, m2 が含まれていたら、 m3 も自動的に link される。
・(補完モジュールの選択アルゴリズムの詳細は省略。)
・以上述べたことを除けば、 complements 宣言は extends 宣言と同じ。 (つまり、 m3 は必ず m1, m2 の下に link される。)
・ imports a.b.* と宣言されていれば、そのモジュール内で、 a.b.* というクラス名を単純名でアクセスすることができるようになる。 imports a.b.C というふうに単一のクラスを指定することもできる。 これにより、 a.b.C を C という単純名でアクセスすることができる。
ただし、 a.b は Java のクラスライブラリの package 名でなければならない。 MJ の module 名を imports 宣言で指定するとコンパイル時エラーになる。 (コンパイラは、 MJModuleLoader という名前のクラスを持つ package は、 Java の package ではなく MJ の module と見なす。)
・ imports 宣言は、 sub-module に継承される。 例えば super-module が imports a.b.* と宣言していれば、 すべての sub-module は自動的に imports a.b.* の宣言をしたことになる。 (名前空間を汚染しないために、できるだけ module graph の上の方では imports 宣言を用いない方が望ましい。)
・ imports で宣言したクラス名と、名前空間の継承で参照可能になったクラス名で 同一のものがある場合、その名前を単純名で参照すると ambiguous error になる。 その場合は、どちらかを完全限定名で指定する必要がある。
・クラス、インターフェース、メソッドを定義するときには、 "define" を先頭につける必要がある。 "define" がついていないと、コンパイラは、 「定義」ではなく「拡張」であると判断する。
・フィールド定義には "define" をつける必要はない。 コンパイラは、常に「定義」であると判断する。 super-module で同じ単純名のフィールドが定義されている場合でも、 コンパイルエラーにはならない。 (ただし、アクセスには完全限定名が必要になる。)
・あるモジュールが参照する名前は、そのモジュール自身か、 そのモジュールの先祖モジュールの中の ちょうど1箇所で定義されている必要がある。
・クラスを定義するときは、 superclass, superinterface に、 Java class か MJ class を指定することができる。 superclass を指定しない場合は java.lang.Object が superclass になる。 クラス定義には abstract modifier を指定することができる。 public/protected/private が指定されると、現在の実装では無視される。 その他の modifier については、どういう動作になるか不明。
・メソッド定義は、 abstract にすることができる。 public/protected/private が指定されると、現在の実装では無視される。 その他の modifier については、どういう動作になるか不明。 throws 宣言は、多分正しく動作する。
・フィールド定義も、 public/protected/private が指定されると、現在の実装では無視される。 static と final については、正しく処理される。 その他の modifier については、どういう動作になるか不明。
クラスの拡張では、以下のことができる。
インターフェースの拡張では、以下のことができる。
・ "define" がついたメソッド断片は definition method と呼ぶ。
・メソッド断片を1列に並べたとき、 最初が definition でなくてはならず、 最初以外は definition であってはならない。
・ Java method は、 Java 言語の世界で定義されているので、 それを MJ class が継承して override する場合は "define" を 付ける必要はない。
・ interface のメソッドについて。 (省略。)
・クラス定義時に abstract modifier を付けたクラスは、 abstract class 、 定義時に abstract modifier を付けたメソッドは abstract method と呼ぶ。 (MJ には abstract constructor もある。以下の説明で使う abstract method という言葉はabstract constructor も含むものとする。)
・ Java 言語では、 abstract でないクラスで abstract method を定義・継承することは許されないが、 MJ では許される。
ただし、リンク時に「 abstract method の実装もれ」がチェックされる。 リンク結果のプログラムにおいて、 abstract でないクラスが abstract method を持っているときは、リンク時エラーとなる。
・ Java 言語は abstract でないメソッドを abstract method で override することができるが、 MJ ではできない。
・メソッドを拡張する際には、オリジナルのメソッドの throws 宣言と 同一の throws 宣言を書かなければならない。 (宣言を減らしたり増やしたりしてはならない。)
なお、 Java 言語と同様に、 subclass で superclass の throws 宣言を 減らすことはできる。
コンパイラを通ったモジュール同士であっても、 組み合わせて linearize したときにリンク時エラーにすべき場合がある。
例えば、1つの abstract method を別々に「実装」するモジュールを、 同時に使用すると、エラーにすべきである。 これを「実装の衝突」と呼ぶ。 現在の処理系は、このリンク時の実装の衝突のチェックを行っていない。
・ほとんど全ての場所で、クラス名・メソッド名・フィールド名の単純名のかわりに、 完全限定名が使える。
例外として、フィールド宣言では、完全限定名を使うことはできない。 (フィールドには「拡張」がないので、フィールド宣言に FQN は不要。 モジュール m 内のフィールド f の宣言は、必ず m::f という名前の宣言を表す。)
・FQN の指定は、単純名のあいまいさの解消に用いられる。 (Java 言語のように、 import していない名前を参照する手段としては 使えない。)
・あいまいさがない場合、すなわち単純名で正確に1つの対象を指定できる場合に、 あえて FQN を使用してもかまわない。 ただし、その場合、その FQN は、 単純名によって選択される対象の FQN と一致していなければならない。 一致しない場合はコンパイルエラーとなる。
・例として、 method overloading 解決のアルゴリズムについて、 疑似コードで説明する。
まず、ターゲットの型、メソッド単純名、実引数の型で、 呼び出されるメソッドの候補をしぼる。 (この段階では、メソッドの完全限定名の情報は使わない。) 候補が1つもなければ、 No method matching xxx found. 候補が1つ以上あれば、その中から most specific method を選ぶ。 most specific method が 1つもなければ、 ambiguous error 。 most specific method が 1つあった場合、 プログラマがメソッド単純名で呼び出していたなら、呼び出し先確定。 プログラマーが FQN で呼び出していたなら、 候補の FQN とプログラマが指定した FQN が一致するかを調べ、 一致しているなら、呼び出し先確定。 一致していないなら、 No method matching xxx found. most specific method が複数あった場合、 プログラマがメソッド単純名で呼び出していたなら、 ambiguous error 。 プログラマーが FQN で呼び出していたなら、 候補の中から FQN が一致するものを選ぶ。 見つかったならば、呼び出し先確定。 見つからなかったら、 No method matching xxx found.
ただし、 most specific method とは、全ての i に対し i 番目の仮引数の型 T1_i が、 他の全ての候補メソッドの i 番目の仮引数の型 T2_i の、 subtype か等しい型であるものを言う。 (Java では、 most specific method が1つ存在するか、 1つも存在しないかのどちらかだが、 MJ では、異なる完全限定名を持った複数の most specific method が 存在する可能性がある。)
・ Java 言語で a.b.C という「クラス名の完全限定名」が使える場所では、 MJ でも同様に a.b.C と書ける。その場所で FQN[a.b::C] と書いてもかまわない。 Java 言語で「クラス名の単純名」しか許されない場所では、 単純名か FQN[a.b::C] のみが使え、 a.b.C という書き方は許されない。
・ Java class で定義されたフィールド・メソッド名 n の 完全限定名は FQN[java::n] となる。
現在のところ、 Statement に関しては Java 言語と全く変わらない。
・ Java class, MJ class の呼び出しは普通に行える。 例:
c.foo();
・メソッド名に完全限定名を用いることができる。 例:
c.FQN[m::foo](); FQN[m::foo]();
・自分自身(および自分の superclass)のフィールドは、普通にアクセスできる。 例:
f
・自分以外の Java class, MJ class のフィールドにも普通にアクセスできる。 例:
c.f
(ただし、自分以外の MJ class のフィールドアクセスには、将来は制限が付く予定。 class invariant を破壊する恐れがあるので避けるべきである。 直接アクセスせずに、アクセスメソッドを通すことを推奨。)
・フィールド名に完全限定名を用いることができる。 例:
x = c.FQN[m::f]; x = FQN[m::f];
・ Java では subclass の field が super class の同名の field を hide するが、 MJ では field の hide は起きない。 単純名でそのフィールド名を参照した場合、 ambiguous error になる。 完全限定名を指定すれば、どちらの field も参照することができる。
・ローカル変数で同名の field を shadow することは、 Java 同様にできる。
・外側のローカル変数・メソッド引数と同じ名前のローカル変数を 内側で宣言することは、 Java 同様できない。宣言するとコンパイルエラーとなる。
・ original 呼び出しの signature は、その original 呼び出しが含まれるメソッドと 一致していなければならない。 ( Java では super.foo(args) という構文によって、自分とは異なる signature の、 super のメソッドを呼び出せるが、 MJ ではできない。)
・ Java class/interface の anonymous class を作ることができる。 MJ class/interface の anonymous class は、現在の実装はサポートしない。 つまり、 new A(){...} と書いたとき、 A が MJ 言語で定義された class または interface である場合は、コンパイルエラーになる。
・ anonymous class は、だいたいは、 Java と同じように使える。 ただし、(現在の実装では、) outer class の field/method にアクセスすることはできない。 OuterClass.this という構文もサポートしていない。 この問題に対処するには、次のようにする。
final Draw outer = this; // outer という final 変数を宣言 b.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ Graphics g = outer.getGraphics(); // outer class の method 呼び出し ...; }});
・モジュール名はすべて小文字。 (package 名が Java のクラス名と衝突すると javac が わかりにくいエラーメッセージを出してしまうので、それを避けるため。)
・モジュール名には、 a.b.c というふうに identifier を "." で 区切ったものも使える。 ドメイン名の prefix を付ける必要はない。 (モジュール名の衝突は、外部ツールによる rename によって対処する。)
・モジュールの rename をするツールを提供する予定。(未実装)
mjrnp in.jar out.jar foo jp.go.etl
で、 in.jar の中に現れる "foo." ではじまる module 名が、 "jp.go.etl." ではじまる module 名にに変換される。 (開発するときは短い名前で開発し、配布するときにドメイン名をつける、 という使い方を推奨。)
・ "java" というモジュール名は使えない。
・ Java のパッケージ名として許されないものは、 MJ の module 名でも許されない。 (例えば p.class.a は、 class というキーワードを含んでいるのでエラーになる。)
・外部インターフェースを定義するモジュールには短い名前を、 内部実装モジュールを定義するモジュールには、 postfix を付けて長くした名前を付けるとよいと思う。
例: p.collection と p.collection.implementation
具体的にどういうスタイルで名前を付けるのがよいかは、今後考える。
・ MJ class 内部での member class, block local class の定義はできない。
・ Java class の member class の継承はできない。
・クラス、メソッドを final にできない。 (field とローカル変数は final にできる。)
・ abstract/public/protected/private/final 以外の modifier については、 どういう動作になるか不明。
・ #+comment
(省略)
ここでは Java 言語の文法に対する差分のみを記述する。
<original alternatives> は、 Java 言語における選択肢を
表している。
大文字で始まる記号は非終端記号、それ以外は終端記号を表す。
<e> は空文字列を表す。
MJCompilationUnit: MJModule*
MJModule: ; { MJModule* } #+ SharpPlusExpression MJModule #- SharpPlusExpression MJModule module MJModuleName MJModuleHeaderDeclaration* { MJTypeDeclaration*}
MJModuleHeaderDeclaration: complements MJModuleNameList extends MJModuleNameList uses MJModuleNameList imports MJImportedPackageNameList
MJModuleNameList: MJModuleName MJModuleName , MJModuleNameList
MJImportedPackageNameList: MJImportedPackageName MJImportedPackageName , MJImportedPackageNameList
MJImportedPackageName: Name Name . *
MJTypeDeclaration: MJClass ; #+ SharpPlusExpression MJTypeDeclaration #- SharpPlusExpression MJTypeDeclaration
MJClass: MJDefine Modifier* MJClassKeyword MJName Extends Implements { MJClassBodyDeclaration* }
MJClassKeyword: class interface
MJClassBodyDeclaration: MJFieldDeclaration MJConstructorDeclaration MJMethodDeclaration ; #+ SharpPlusExpression MJClassBodyDeclaration #- SharpPlusExpression MJClassBodyDeclaration
MJFieldDeclaration: MJDefine Modifier* Type VariableDecorators // FQN is not allowed.
MJConstructorDeclaration: MJDefine Modifier* MJName ( FormalParameterList ) Throws MJMethodBody
MJMethodDeclaration: MJDefine Modifier* Type MJName ( FormalParameterList ) Throws MJMethodBody
MJDefine: <e> define
MJMethodBody: ; Block
Statement: <original alternatives> #+ SharpPlusExpression Statement #- SharpPlusExpression Statement
Primary: <original alternatives> original ArgumentList MJFQN // this field access MJFQN ArgumentList // this method invocation Expression . MJFQN // field access Expression . MJFQN ArgumentList // method invocation Name . MJFQN // static field access Name . MJFQN ArgumentList // static method invocation MJFQN . MJFQN // static field access MJFQN . MJFQN ArgumentList // static method invocation new MJFQN ... // instance creation
Type: <original alternatives> MJFQN
MJName: Identifier MJFQN
MJFQN: FQN [ MJModuleName :: Identifier ] // This "FQN" is not a non-terimnal symbol, but a token.
MJModuleName: Name
SharpPlusExpression: Identifier String // e.g. "debug=true"
VariableDecorators: <original alternatives>
ArgumentList: <original alternatives>
Expression: <original alternatives>
Name: <original alternatives>
Modifier: <original alternatives>
Identifier: <original alternatives>
String: <original alternatives>
Last updated:
Dec 19 14:16:52 2001
|