モジュールとライブラリ

ここでは、モジュールの概要と、ライブラリのインポートについて扱います。

- 目次 -

モジュールとライブラリ

ライブラリとは

ライブラリとは、よく使う関数や定数などをまとめて記述した、部品的なプログラムの事です。

モジュール

これまでの内容では、プログラムは全て1 枚のファイルに記述し、処理内容もその中で完結していました。 しかし、ライブラリを使用したプログラムの場合は、いくつものファイルに記述された処理が、組み合わさって動作します。

この時、各ファイルの記述内容を、1 枚のファイルに結合して実行する事もできます。 しかし、それでは同じ変数名や関数名が競合していた場合に不都合が生じます。 そのため、通常は、ファイルごとに独立した、「 モジュール 」という形で読み込まれて実行されます。

このように書くと抽象的で分かり辛いですが、 VCSSL では基本的に1 枚のファイルが 1 つのモジュールに自動対応する仕組みになっているため、 モジュールとはそれぞれのファイル内容の事だと思っても問題はありません。 A という名前のファイルに書かれたプログラムは「 A モジュール 」という具合です。

実行モジュール と ライブラリモジュール

実行モジュールとは、これまでのように(別のモジュールから使用されるのでは無く)、 普通に直接実行されるモジュールの事です。

それに対してライブラリモジュールとは、そのものを直接実行されるのでは無く、 別のモジュールから読み込まれて使用される、部品的なモジュールの事です。

ライブラリのインポート

ライブラリモジュールに用意された関数を、 別のモジュール(別ファイルに記述されたプログラム)から使用するには、 そのモジュールの先頭領域でimport 宣言を行います:

import ライブラリパス;

ライブラリパスは、実行モジュールからの相対パスを、 ドット記号「.」区切りで指定します。 また、拡張子「.vcssl」は不要です。 ライブラリモジュールのファイルが実行モジュールと同じ場所(フォルダ内) に存在する時は、ライブラリモジュールのファイル名を記述するだけで使用できます。

例えば実行モジュールと同じフォルダ内にある「 lib.vcssl 」を使用するなら:

ImportLib.vcssl

これで、lib.vcssl に記述されている全ての関数と定数が利用可能になります。

また、実行モジュールから見て、「aaa 」フォルダ内の、さらに「bbb 」フォルダ内にある「 lib.vcssl 」を使用するなら:

ImportAaaBbbLib.vcssl

とします。

別モジュールの関数や変数を使用する

別モジュールの関数や変数を使用するには、同じ関数名や変数名が競合していない場合、 何も特別な事をする必要はありません。これまでと変わらず、普通に変数や関数を呼ぶ事ができます。

例えば、実行モジュールA から、モジュールB の関数を呼ぶ場合は、以下のようにします:

- 実行モジュール A ( A.vcssl ) -

A.vcssl

- ライブラリモジュール B ( B.vcssl ) -

B.vcssl

上の例で、実行モジュールA ( A.vcssl ) を実行すると、 VCSSL コンソールに「 call B.fun 」と表示されます。

モジュールA にはfun 関数は宣言されていませんが、fun( )と呼び出しています。 そこでモジュールB に宣言されているfun 関数が呼ばれたわけです。

モジュールの優先度

上に示した例では、関数や変数の名前(関数の場合は引数仕様も)が競合している場合、 問題になる事があります。

例えば、以下のように記述し、実行してみてください。

- 実行モジュール A ( A.vcssl ) -

A.vcssl

- ライブラリモジュール B ( B.vcssl ) -

B.vcssl

上の例で、実行モジュールA ( A.vcssl ) を実行すると、 VCSSL コンソールに「 call A.fun 」と表示されます。 つまり、モジュールB のfun 関数では無く、モジュールA のfun 関数が呼ばれたわけです。

このように、関数を呼び出す場合、自身のモジュール内に宣言された関数が最優先されます。

そして、自身のモジュール内に見つからない場合、 import しているモジュール内が探されます。 複数存在した場合は、後にimport されたものが優先されます。

それでも見つからないという場合は、別モジュール間のimport などによって、 処理系に読み込まれた全てのモジュールから探されます。 優先度は、後に読み込まれたものが高くなります。以下に例を挙げます:

- 実行モジュール A ( A.vcssl ) -

A.vcssl

- ライブラリモジュール B ( B.vcssl ) -

B.vcssl

- ライブラリモジュール C ( C.vcssl ) -

C.vcssl

上の例で、実行モジュールA ( A.vcssl ) を実行すると、 VCSSL コンソールに「 "call C.fun" 」と表示されます。

このように、モジュールA からはモジュールB しかimport していないにも関わらず、 モジュールC の fun 関数を呼ぶ事ができています。 これは、モジュールB がさらにモジュールC をimport していて、処理系がモジュールC の存在を知っているためです。

ただしこの機能は、モジュール数が増加すると優先度の把握が難しくなり、可読性も低下します。 そのため、原則として、使われる側のモジュールは、 使う側のモジュールで明示的にimport しておく事が推奨されます。 つまり上の例では、モジュールA でも「 import C ; 」しておく事が推奨されます。

所属モジュールの明示

これまでに示した、優先度による自動的なモジュール判定は、小規模なプログラムの場合には便利です。

しかしモジュールの数が多くなってきたり、第三者が開発したライブラリモジュールを組み合わせて使用するような場合、 どのモジュールにどういった名前・引数仕様の関数があるのかを、常に完璧に把握しておくのは困難です。 その場合、関数名の競合が発生しているのを見逃してしまうと、 意図していたのと異なるモジュールの関数をコールしてしまうミスを招きかねません。

このような場合、関数名や変数名の前にドット記号「.」区切りでモジュール名を明示する事ができます:

- 実行モジュール A ( A.vcssl ) -

A.vcssl

- ライブラリモジュール B ( B.vcssl ) -

B.vcssl

- ライブラリモジュール C ( C.vcssl ) -

C.vcssl

上の例で、実行モジュールA ( A.vcssl ) を実行すると、 VCSSL コンソールに、改行を挟んで「 call B.fun 」「 call C.fun 」と表示されます。

このようにモジュールを明示する事で、競合を見落として意図しないモジュールの関数を呼んでしまうのを防げる上に、 可読性も向上します。

アクセス修飾子

上で述べた所属モジュールの明示は、意図しないモジュールの関数・変数を呼んでしまう(アクセスする)事を防ぐ手段ですが、 いわば「 呼ぶ側 」の手段です。

これに対して「 呼ばれる側 」でアクセスを制御する手段も存在します。それがアクセス修飾子です。 VCSSLのアクセス修飾子には、「 private 」と「 public 」の2種類が存在します。

例えば、モジュール外からは呼んでほしくない関数・変数や、全く呼ぶ必要が無い関数・変数があったとします。 こうした場合、関数・変数宣言の先頭に「 private 」と記述する事で、モジュール外からは呼べなくなります(存在しないのと同一に扱われます)。

逆に、関数・変数宣言の先頭に「 public 」と記述すれば、モジュール外から自由に呼ぶ事ができます。 ただし、VCSSLではこれまでのように普通に関数や変数を宣言すると、標準で public になります。

具体的な例を見てみましょう:

Access.vcssl

上の例では、変数 a 及び関数 funA はアクセス修飾子「 private 」が付いているので、モジュール外から呼ぶ事はできません。

逆に、変数 b 及び関数 funB はアクセス修飾子「 public 」が付いているので、モジュール外から自由に呼ぶ事ができます。

ところで、「 public 」をわざわざ付けても、普通にアクセス修飾子を省略して宣言した時の挙動と同じなので、機能的にはあまり意味はありません。 しかしながら明示的に付けておく事で、「 private 」を付け忘れたのでは無く、安心して外部から呼んでも大丈夫な変数・関数である事を表明する事ができ、可読性に貢献します。

モジュールの唯一性

モジュールには唯一性が保たれます。具体的には、複数回import されたモジュールでも、 その実体は 1 つしか無いという事が保証されます。例えば以下のように記述し、実行してみてください:

- 実行モジュール A ( A.vcssl ) -

A.vcssl

- ライブラリモジュール B ( B.vcssl ) -

B.vcssl

- ライブラリモジュール C ( C.vcssl ) -

C.vcssl

このプログラムを実行すると、VCSSL コンソールに 「 2 」 と表示されます。 これはつまり、モジュールAからimport したモジュールC と、モジュールBからimport したモジュールCは、 同一のものであるという事です。

ライブラリモジュール内での相対パスの扱い

ライブラリモジュールの中から、何らかのファイルへのアクセスを行う場合は、ファイルパスの指定に注意が必要です。

というのも、ライブラリモジュールからファイルへアクセスする際のファイルパスには、そのライブラリから見た相対パスでは無く、実行モジュールから見た相対パスを指定しなければならないからです。

例えば、ライブラリの中でさらに別のライブラリをインポートする場合、ライブラリパスには、 実行モジュールから見た相対パスを指定する必要があります。 さらに、ライブラリの中でファイル入出力関数を使用する場合や、画像のロード等を行う場合にも、実行モジュールから見た相対パスを指定する必要があります。

各モジュールのグローバル領域実行順序

本文書の前半でもずっと行ってきた通り、VCSSL では、モジュールのグローバル領域に通常の処理を記述する事ができます。

この時に問題になるのが、各モジュールのグローバル領域が、どのような順序で実行されるかです。 VCSSL では、import の階層をツリー状に表した際、深い階層から順に実行されていきます(実行モジュールが最も浅い階層となります)。 同じ深さの階層では、先にimport されたものから順に実行されます。

グローバル定数の初期化順序

const キーワードを付けて宣言された変数は、初期化後に値を変更できない定数となります:

Const.vcssl

グローバル領域でこのように宣言された定数、いわゆるグローバル定数は、 グローバル領域の実行よりも早いタイミングで初期化されます。

そのため、グローバル領域の実行が始まった時点では、 全てのモジュールのグローバル定数は初期化されています。

複数ファイルを1つのモジュールにまとめて読み込む

import に似ている機能に、include というものがあります。使い方は、import の変わりにそのままinclude と記述するだけです。

include は、別ファイルを独立したモジュールとして読み込むのではなく、 ファイルの記述内容を、その場所にそのまま埋め込む機能です。 つまり、include したファイルと、include されたファイルは、内容が結合され、同じ1 つのモジュールとして読み込まれます。

この機能は、似ているけれども微妙に異なるモジュールが複数必要な場合などに、 共通部分を別ファイルに記述して使用するためにサポートされています。 VCSSL では、同一モジュール内で、同名・同引数仕様の関数を複数宣言した場合、 後に(ファイル下方で)宣言したもので上書きされます。 これを利用して、include したファイルの関数を上書きし、微妙に異なるモジュールを作る事ができます。