すべてのソースファイルのグローバルなスコープはモジュールと呼ばれるスコープとして管理されます。 関数やグローバル変数、クラスなどはモジュールのメンバと考えることができます。 このモジュールの概念によって、異なるの開発チームのライブラリを同時に利用するときに名前が衝突するような自体を避けることができるようになります。
モジュールはクラスとは異なりインスタンスが常に 1 つで、親や子などの継承関係を持ちません。 ソースファイルはモジュールに対応し、モジュールの名前はソースファイル名からパスと拡張子を除いた名前となります。
他のモジュールと同時にコンパイルを行う場合、モジュールにアクセスするファイルは import 宣言を必要としました。 これまで、標準入出力を行うためにファイルの先頭で何度も宣言していました。 import 宣言は次のように行います。
import モジュール名1, モジュール名2 ... ;
import 宣言は、同時に複数のモジュールを指定することができます。 この宣言で指定されたモジュールは C 言語の #include とは異なり、テキストをそのまま接合するのではなく、シンボルだけを読み込むのです。 そのため、複数のソースファイルがコンパイラに読み込まれ、シンボルが衝突するようなマヌケを避けることができます。
D 言語における import 宣言は Java のように、長い名前の完全限定名(名前空間を含む識別氏のフルネーム)を省略する目的だけではなく、シンボルを指定したファイルから読み込むという意味があることを忘れてはなりません。
//Point.d class Point { int x , y; this(int x , int y) { this.x = x; this.y = y; } void X(int x) { this.x = x; } int X() { return x; } void Y(int y) { this.y = y; } int Y() { return y; } }
//test.d import std.stream; import Point; int main() { Point pt = new Point(10 , 100); stdout.printf("X=%d,Y=%d\n" , pt.X , pt.Y); return 0; }
このプログラムは、Point.d と test.d という 2 つのモジュールに分割してコンパイルしています。 test.d は Point.d モジュールで宣言されている Point クラスにアクセスするため、import 宣言を用いて Point モジュール内のシンブルを読み込んでいます。
複数のモジュールを import 宣言で読み込んでいる場合、モジュール内部で定義されている識別子が衝突する可能性があります。 識別子が衝突している場合、どのモジュールの識別子を参照しているのかが曖昧になってしまうため、明示的にモジュール名を指定する必要があります。
モジュール名 . 識別子
このように、ドット . 記号を用いて識別子のモジュールを明示的に指定することができます。
//Geom.d import std.stream; class Point { int x , y; this(int x , int y) { this.x = x; this.y = y; } void Print() { stdout.printf("Geom.Point X=%d, Y=%d\n" , x , y); } }
//Graphics.d import std.stream; class Point { int x , y; this(int x , int y) { this.x = x; this.y = y; } void Print() { stdout.printf("Graphics.Point X=%d, Y=%d\n" , x , y); } }
//test.d import std.stream; import Geom; import Graphics; int main() { Graphics.Point pt1 = new Graphics.Point(10 , 100); Geom.Point pt2 = new Geom.Point(64 , 128); pt1.Print(); pt2.Print(); return 0; }
このプログラムでは Graphics モジュールと Geom モジュールで Point クラスが衝突しています。 このままで test.d から Point クラスを参照すると曖昧になってしまうため、test.d からは Graphics.Point と Geom.Point という完全限定名を指定することでこの問題を回避しています。
デフォルトでモジュールはファイルと関連付けられ、パスと拡張子を除いたファイル名がモジュール名となります。 しかし、いくつかのファイルを同じモジュールにまとめたり、ファイル名がモジュール名に関連付けられることが問題になる場合、module 宣言 を明示することによってソース上でモジュール名を指定することができます。
module モジュール名 ;
module 宣言では、module キーワードの直後にソースコード全体にかかるモジュール名を指定します。 module 宣言は、ソースコードの先頭に記述しなければならず、他のあらゆるトークンよりも先に現れなければなりません。
module Geom; import std.stream; class Point { int x , y; this(int x , int y) { this.x = x; this.y = y; } void Print() { stdout.printf("Geom.Point X=%d, Y=%d\n" , x , y); } }
import std.stream; import Geom; int main() { Point pt = new Point(64 , 128); pt.Print(); return 0; }
このプログラムの Geom モジュールは module 宣言によって明示的に宣言されているためソースファイル名は関係ありません。 Point クラスへは Geom モジュールとしてアクセスすることができます。
module 宣言を用いてモジュール名を明示すると、モジュール名にパッケージ名を追加することができます。 パッケージは、多くのモジュールまとめるためのもので、例えばライブラリを開発したベンダ名などをパッケージ名にすることで、他者のライブラリとの競合を避けることができます。 std . stream の std はこのパッケージ名にあたります。
module パッケージ名 . モジュール名 ;
パッケージ名は wisdom . Graphics . Drawing というように階層化することも可能です。
module Graphics.Geom; import std.stream; class Point { int x , y; this(int x , int y) { this.x = x; this.y = y; } void Print() { stdout.printf("Graphics.Geom.Point X=%d, Y=%d\n" , x , y); } }
import std.stream; import Graphics.Geom; int main() { Point pt = new Point(64 , 128); pt.Print(); return 0; }
このプログラムは、module 宣言にパッケージ名を追加した例です。 Geom モジュールが Graphics パッケージに属していることがわかります。 この場合、モジュールをインポートするコードの import 宣言でもパッケージ名を明記しなければなりません。