MJ の言語仕様に関する質問

MJ はアスペクト指向言語なのですか?

MJ は広い意味でのアスペクト指向言語です。 複数のクラスを横断するコードを、別のモジュールに分離して記述できます。

しかし、 MJ は代表的なアスペクト指向言語であるAspectJとはかなり考え方が異なります。 AspectJ はデバッグ、同期、性能向上など、プログラム本来の機能とは異なるアスペクトを分離することに主眼を置いていますが、MJ はそれらを重視していません。

MJ が解決しようとする問題は、どちらかといえば、Hyper/Jに近いものです。 従来の手続き指向言語あるいはオブジェクト指向言語は、1種類のモジュールの切り分け方(手続きまたはクラス)をプログラマーに強制するものですが、Hyper/J や MJ は自由な切り分け方を可能にするシステムです。

また、MJ は、言語仕様のシンプルさと、モジュール結合時の安全性を第一に考えて設計しています。この点は、他の AOP システムと大きく異なる点です。

「差分ベースモジュール」は Java 言語以外に適用できますか?

オブジェクト指向言語でなくても、手続き型言語、論理型言語、ハードウエア記述言語などにも適用できると思います。 多くのプログラミング言語は、なんらかの記述単位に「名前」を付けます。 MJ のモジュール機構のアイデアは、オリジナルのプログラムの「名前」に対応付けられたコードに、なんらかの形で差分を追加する、というシンプルなものです。 したがって、多くのプログラミング言語に適用することができます。

例えば、差分ベースモジュールを emacs-lisp に適用したものが、 dm-elisp です。

MJ のモジュール機構を適用できない言語もあります。 例えば項書換え系は、記述単位に名前を付けないので、適用できません。

モジュールの多重継承ができるのは好ましくないのでは?

多重継承の問題のうち、名前の衝突に関しては、フィールド・メソッドの完全限定名の導入によって完全に解決しています。

もう一つの問題、意味的衝突については、 Design by Contract および behavioral subtyping の考え方を差分ベースモジュールに応用することで解決できます。(詳しくは この論文 を参照してください。)

しかし、 tree 構造の方が一般のグラフ構造よりも人間にとって扱いやすいことは確かです。 MJ におけるプログラミングでも、複雑なモジュールの多重継承を推奨しているわけではありません。 モジュールの継承グラフは、できるだけシンプルな形になるようにプログラミングした方がよいと思います。

理想的なモジュールの継承グラフは、継承の深さが浅く、横に広い形をした継承グラフです。 継承グラフがそのような形をしていれば、プログラムの保守が容易になります。

MJ のモジュールと Java の package との違いは?

Java には class という構文とは別に package がありますが、役割分担が完全にできていません。 package も class も、ともに名前空間の役割を果たしています。 MJ は、クラスの役割とモジュールの役割を整理して分けることで、言語仕様を Java よりもシンプルにしています。

拡張性・再利用性を高くしようとすると、モジュール分けに時間がかかってしまうのでは。

XP が言っているように、最初から拡張性・再利用を考えたモジュールの切り分けに時間を割くことはしない方がよいと思います。

この言語に限らず、拡張性・再利用性を高くするには労力が必要です。 先々のことを考えて拡張性・再利用性の高いモジュール分けを行っても、実際に拡張や再利用が行われなければ、その苦労は報われません。 プログラマーは、そのトレードオフを考えて時間配分を行わなければなりません。

OOP のいいところは、記述対象をクラスにモデル化して記述さえすれば、それだけで、自動的にある程度拡張性・再利用性の高いプログラムができるという点です。 つまり、「クラス=モジュール」は、近似的には成り立つ場合が多いので、クラスの設計さえすれば、それだけで、そこそこよいモジュール分けができてしまうわけです。 これもオブジェクト指向パラダイムのメリットの1つだと思います。

このメリットは、 MJ にも引き継がれています。 完璧なモジュール分けに頭を悩ます時間がなければ、従来言語と同様、1クラス1モジュールで書いておけばいいのです。 開発・保守が進んで行くうちに問題ができたら初めて、モジュールの分け方を見直せばよいのです。

モジュールの分け方を変更する作業はリファクタリングと呼ばれています。 将来的には MJ でのリファクタリングをサポートするツールができれば便利だと思います。

この言語で書いても、モジュラリティの悪いプログラム、拡張性の悪いプログラムは書けるのでは?

そのとおりです。 オブジェクト指向設計とリファクタリングは、 MJ でのプログラミングでも重要です。

1つのクラス定義が複数のモジュールに切り刻まれると読みにくいのでは?

同様の批判が、従来のオブジェクト指向言語にも言われていたことを思い出して下さい。 「1つのクラスの定義が、スーパークラスとサブクラスに分離されているため、プログラムが読みにくい」という批判です。 この批判に対する答えは2つあります。

1つは Design by Contract の実践です。 個々のクラスやメソッドの外部仕様を明確にしておけば、そのクラスやメソッドの内部実装のコードをいちいち見に行く必要がありません。 つまり内部実装が複数の場所に分散していても問題にはなりません。 むしろ、情報隠蔽によって不必要なクラスやメソッドの名前が見えなくなることにより、プログラムの可読性は増すことになります。 この答えは、差分ベースモジュールにもそのまま当てはまります。

もう1つは、 UML などを使った外部ドキュメントです。 オブジェクト指向言語が扱う問題は従来の言語が扱う問題よりもはるかに複雑で、もはやソースコードを読むだけで全体像を理解することは不可能です。 そこで、 UML などを使った理解が必要になります。 この答えも、差分ベースモジュールにそのまま当てはまります。 UML のクラス図を差分ベースモジュール用に拡張した、レイヤードクラス図を使えば、モジュールがプログラムを拡張する様子を視覚的に分かりやすく表現することができます。

クラスの多重継承はサポートしないのですか?

クラスの多重継承はオブジェクト指向言語に必要な機能だと思います。 しかし、 MJ が JavaVM の上で実行される言語である限りは、クラスの多重継承をサポートする予定はありません。 現在の JavaVM 上でクラスの多重継承を実装すると、どうしても実行速度が遅くなります。

名前空間の import と inheritance は別の概念であり、分けるべきでは?

Szyperski, C.A.: "Import is Not Inheritance, Why We Need Both: Modules and Classes", In Proc. of ECOOP'92.
という論文では、クラスの継承をライブラリの import の変わりに使うべきではないと主張しています。

しかし、本来は異なる概念を1つの言語機構で実現することは、プログラミング言語を設計する上でよく行われます。 例えば、 Java では class を情報隠蔽の単位、型定義の機構、排他制御の単位、メモリの動的割当の単位などに用いています。 このように1つの言語機構に多くの機能を unify することで、言語の多機能さと簡潔さという矛盾する要求を同時に満たすことが可能となります。

MJ は、クラスから名前空間の機能を分離する代わりに、名前空間と差分プログラミングの機構を unify しました。 この試みは成功しているようです。

import と inheritance は、もともと機能的に良く似ています。 Eiffel 言語の文化では、継承機構を積極的に「ライブラリの取り込み機構」として利用しています。 例えば、クラスの中から標準入出力のライブラリを使う時には、その機能を提供するクラスを継承します。 (ただし、 Eiffel ではライブラリのインターフェースは、暗黙のうちに継承されることはありません。)

public/private がなくなりましたが、セキュリティメカニズムはどうなりますか?

「sandbox の外側」、つまり「安全でないコードを動的にロードして実行するアプリケーション」の記述には、現在の MJ は使えません。 セキュリティの観点からは、 MJ で定義されたクラス名・フィールド名・メソッド名はすべて public であると思ってください。

多重継承における名前の衝突を rename で回避することはできないのですか?

名前の衝突の問題は、メソッド完全限定名の導入により解決できています。 しかし、メソッド完全限定名だけでは解決できない種類の衝突があります。 例えば、 module m1 で定義されている abstract method foo を、m1 の sub-module である m2, m3 でそれぞれ実装していて、m2, m3 を同時に継承する sub-module m4 があるという状況を考えます。 このとき、 m4 から見ると、m2 か m3 のいずれかの foo の実装が、 override によって消えてしまう、という問題があります。

もし m4 が、 m2 と m3 の両方の foo の実装を生かしたいと思ったら、Eiffel 言語のようなメソッドの rename の機構が必要になるでしょう。 この機構については、将来、導入したいと思っています。

FQN のシンタックスがひどいと思います。

現在のようなシンタックスになっているのは、Java 言語の「パッケージ名の区切りにもメンバーの参照にも "." を使っている」という問題に起因します。 この問題が解決できれば、もっとまともなシンタックスにできると思います。 ( XML のように名前空間の別名を prefix に使うようにすれば、シンタックスがすっきりするでしょう。これは将来のバージョンで実装しようと思います。)

MJ では依存する名前空間を最小にできるので、名前の衝突が起きる確率は従来のオブジェクト指向言語よりもかなり低くなると予想しています。 実際のプログラミングの際に、プログラマーが FQN を指定しなければならなくなることは、あまりないと思います。

m1 を m2 が extends すると、 m1 内部で定義された field が m2 から見えてしまいます。 これでは情報隠蔽にならないのでは?

MJ において「 m1 を m2 が extends する」ということは、Java の nested class で表現すれば「クラス m1 の内部に m2 が定義されている」に相当する、と理解してください。 内側からは、外側で定義されているすべての名前にアクセスできます。

MJ では、すべての名前のアクセス修飾子は "protected" である、と見ることもできます。 普通の Java で super class の protected field が subclass から見えるのと同様、MJ では super-module の中のすべての名前が、 sub-module から見えます。 そのかわり、 m1 を extends するすべてのモジュールのプログラマーは、m1 の利用者ではなく、実装者の立場になることを意識してプログラムしなければなりません。

現在の言語仕様にはありませんが、将来的には "final module" のような機構を入れるかも知れません。 これは Java の final class のように、sub-module の定義を許さないモジュールです。 final module の内部で定義される名前はすべて "private" である、と言えます。

final module 機構は、foolproof のための機構です。 あるプログラマーが、クラスの外部インターフェースだけでなく内部実装にも依存したプログラムを書くと、ペナルティを受けるのは明らかにそのプログラマーです。 将来そのクラスの内部実装が変更になった時に、そのプログラマーは自分が書いたプログラムを書き直さなければなりません。

将来的には Eiffel のような assertion 機構を MJ に入れるつもりですが、それが実現されれば、実装に依存するプログラマーは、さらにペナルティを受けることになります。 クラスの外部インターフェースのみを利用するプログラマーは事前条件だけを満たせば良いのですが、実装に依存するプログラマーは、不変条件と事後条件も満たすようにプログラムしなければなりません。 つまり、より制約の強いプログラミングを強いられることになります。

c extends a, b と書いたとき、 a と b のどちらが上に来るかが決まっていないのは困るのでは?

CLOS などの言語では、 c extends a, b と宣言すると、a が b よりも優先されると解釈されます。 これを local precedence order と呼びます。 これらの言語では linearize の際には、local precedence order も保存されます。

MJ では、エンドユーザがモジュールを組み合わせるときに、できるだけリンク時エラーが起きないようにしたいと考え、現在は local precedence order を言語仕様に入れていません。 次のような例を考えましょう。

module c extends a, b {...}
module d extends b, a {...}

エンドユーザが c と d のモジュールを組み合わせる場合、 local precedence order を保存するような linearize は不可能です。 CLOS などでは、このような場合はエラーになります。

リンク時エラーの原因になるというデメリットを上回るメリットがあれば、将来は、 local precedence order を言語仕様に入れるかもしれません。

モジュールとクラスを分けたのはいいですが、実装の継承とインターフェースの継承(subtyping)も分けるべきでは?

実装の継承とインターフェースの継承が別の概念であることは確かですが、言語機構として別のものを用意すべきだという意見には賛成できません。 subtype は super type と振る舞いを共有する場合が多いので、プログラマーの便宜を図るためには、 subtype を作ったら自動的に実装も継承される方がよいと思います。

また、2つの機構を用意するよりも1つの機構(クラスの継承機構)ですませれば、言語仕様が単純になるという大きな利点もあります。

確かに、「実装は継承したいが、 subtype にはなりたくない」「subtype を作りたいが実装は継承したくない」という場合もあると思います。 前者の場合については、C++ の private inheritance のようなものがあるとよいと思います。 後者の場合については、あらかじめフレームワークの実装者が仕様モジュールと実装モジュールを分けて定義しておくのが、そもそも望ましいと思います。 もしフレームワークがそういう風にかかれていなかったとしても、すべてのメソッドを override することで、 super class の実装を無視することができます。

sub-module は super-module の内側にあるモジュールなのでしょうか?外側にあるモジュールなのでしょうか?

これは、モジュール間のどういう関係に着目するかで、答えは違って来ます。

Java の nested class のイメージで言うなら、 sub-module は super-module の内側にあります。 sub-module からは super-module のすべての名前にアクセスできますが、これは nested class において、 inner class が outer class のすべての名前にアクセスできることに相当します。 一般に、外側のモジュールは抽象的な外部インターフェースであり、内部のモジュールは、より具体的な実装です。 つまり、「内部を隠蔽するという関係」で言えば、 super-module は sub-module を隠蔽しています。

これは実世界におけるモジュールの入れ子関係の対比でも理解できます。 例えば、パソコンのケースの内部にはハードディスク、マザーボードなどのモジュールがあり、それぞれはさらに細かいモジュールから構成されています。 外部のモジュールほど、内部を隠蔽して、抽象度の高い機能を、外部のユーザに提供します。 このように、 super-module と sub-module は、それぞれ実世界における外側のモジュールと内側のモジュールに対応します。

ところが、「随伴関係」と「依存関係」に着目すると、MJ のモジュールと実世界との対応は違ってきます。

実世界のモジュールでは、外側のモジュールの場所を移動すれば、同時に内部のモジュールも移動します。 つまり、内部のモジュールは外部のモジュールに随伴します。 一方 MJ では、 リンク時に sub-module を指定すると、super-module も自動的にリンクされます。 つまり、 super-module が sub-module に随伴するのです。 これはモジュールの入れ子関係とは逆転しています。

最後に「依存関係」という観点で見ると、どうでしょうか。 実世界のモジュールで、例えばマザーボードというモジュールの中に、CPU というモジュールがあります。 マザーボードとCPU は、どちらがどちらに依存しているのでしょうか? (ただし、マザーボードも、CPU も、異なるメーカーから互換性のある複数の製品が利用可能な場合を考えます。) MJ 的な理解では、特定のマザーボードの1製品と、特定のCPUの1製品は、お互いに全く依存していません。 それぞれを接続可能なのは、「CPU のソケットの規格」を両方が正しく実装しているからです。 MJ の構文を使うなら、こういうことになります。

module cpuSpec {...}
module motherBoardSpec {...}
module cpuImplementation extends cpuSpec {...}
module motherBoardImplementation extends cpuSpec, motherBoardSpec {...}

この「CPU のソケットの規格」は、 MJ では1つの仕様モジュールとして表現します。 ところが実世界では規格は紙の上の存在であり、実体を持ったモジュールとしては通常存在しません。 したがって、 MJ での依存関係は、実世界のモジュールどうしの関係とは対応しません。

まとめるとこうなります。

隠蔽関係:super-module が外側のモジュール、 sub-module が内側のモジュール

随伴関係:super-module が内側のモジュール、 sub-module が外側のモジュール

依存関係:super-module に sub-module が依存。実世界には対応しない。

parameterized module はないのでしょうか?

genericsのような、モジュールに型や静的な値を引数として与えて別なモジュールを生成する機能は、現在のMJにはありません。

既存のクラス階層と、拡張されたクラス階層の両方を1つのアプリケーションで使いたいのですが。

現在の MJ ではできません。 将来は、クラス階層のコピーを作る機構を導入する予定です。

1つの差分を、複数のクラスに追加したいのですが。

現在の MJ では、差分を追加する対象となるクラスのクラス名が固定であるため、そのようなことはできません。 将来は、差分の追加対象のクラス名を変更するための機構を導入する予定です。

module をダイナミックロードすることは可能でしょうか。

現在の MJ ではできません。クラスの dynamic loading ならば、 Java 同様にできます。

差分プログラミングの機構としてクラス継承とモジュール継承の両方があるのは冗長で、どちらを使えばよいか迷ってしまいます。

クラス継承は is-a 関係がある場合にのみ使うべきであり、差分プログラミングの道具として安易に使うべきではありません。 この点では MJ は C++ や Java と同じです。 ( Smalltalk は静的型チェックがないせいか、クラス継承を差分プログラミングの道具として使う文化があるようですが。)

before, after, around advice はないのですか?

今の MJ にはありませんが、必要だと思っています。 次のプログラムは before の代わりになると思う人もいるかもしれません。

module m1 extends m0 {
  class C {
    void foo() {
      m1BeforeFoo();
      original();
    }
  }
}

m1 を書いたプログラマーの意図は、「foo の本体を実行する前に、 doBeforeFoo()を実行したい」ということでしょう。これは実現可能です。 しかし、これだけでは不十分な場合があります。 別のプログラマーが、「『自分の super module が追加した foo 本体直前の処理』と『本来の foo 本体』の間に、処理を追加したい。」と思ったとします。 (実際にこのような要求は起こります。) このプログラマーが次のように記述してもうまくいきません。

module m2 extends m1 {
  class C {
    void foo() {
      m2BeforeFoo();
      original();
    }
  }
}

これでは処理 m2BeforeFoo() は、処理 m1BeforeFoo() の前に実行されてしまうからです。

この問題は、あらかじめフレームワーク側が、 before hook を用意しておくことで解決可能です。

module m0 {
  define class C {
    define void beforeFoo(){}
    define void foo() {
      beforeFoo();
      defaultFooBehavior();
    }
  }
}

しかし、フレームワーク構築時点では before, after, around のための hook があとで必要になるかどうか予測することはできない場合があります。 また、ひょっとして必要になるかもしれないから言って、すべてのメソッドに before, after, around hook を入れておくことは、現実的には不可能です。 このような理由から、 before, after, around hook の問題に対処するなんらかの言語機構が必要だと考えています。

なぜ MJ は Java の上位互換の言語にしなかったのですか?

確かに言語研究者は多くの場合、既存の言語への上位互換の形で新しい言語を提案します。 新しい言語を Java の上位互換にすれば、 Java からの移行も容易ですし、記述力に関しては少なくとも Java より悪くなることがないことが保証されるわけです。

しかし、 MJ のモジュール機構のアイデアを、 Java のモジュール機構(package, public/protected/private, nested class)と整合性をたもったまま取り込むのは極めて困難です。 もし、そのような言語を実際に設計したとしても、言語仕様が複雑になり、実装も非常に難しく、実用的言語にすることは不可能だと思います。

「モジュール」は普通、それ単独で何らかの機能を提供するものを言いますが、MJ のモジュールは差分であり、コードの塊にすぎないのでは?

sub-module 単独では機能は持ちませんが、super-module と合わせて考えれば、機能を持ったクラスの集合を提供します。

これは OOP の subclass と同じことです。 subclass のコードだけでは意味を持ちませんが、superclass のコードと合わせて初めて、意味のあるクラス定義になります。


mj-logo
Last updated: Nov 26, 2004