2.5 モジュールと分割コンパイル

ここまでのモジュールの例は全て対話式システム上での例でした。 しかし、モジュールはより大規模なバッチコンパイルされるプログラムに対してもっとも有効です。 そのような類のプログラムでは、ソースを翻訳単位と呼ばれる個別にコンパイルできるいくつかのファイルに分割し、変化があった場合に再コンパイルが必要になる範囲を最小化しなければならない実用上の必要があります。

Objective Camlでは、翻訳単位はストラクチャやシグネチャの特殊なケースで、モジュールシステムの言葉で簡単に説明できます。 翻訳単位 A は二つのファイルからなります

  1. 実装ファイル A.mlstruct ... end の内部と同様に定義の列を含む
  2. インタフェースファイル A.mlisig ... end の内部と同様に仕様の列を含む

両ファイルによりストラクチャ A が、トップレベルで次のように入力されたように定義されます。

module A: sig (* ファイル A.mli の内容 *) end
        = struct (* ファイル A.ml の内容*) end;;
    

これら翻訳単位を定義するファイルは個別に ocaml -c コマンドによりコンパイルできます。(-c オプションは「コンパイルのみ行いリンクを試みない」という意味) コマンドの結果、コンパイルされたインターフェースファイル(拡張子 .cmi)やコンパイルされたオブジェクトファイル(拡張子 .cmo)が生成されます。 全ての翻訳単位がコンパイルされたとき、それら .cmo ファイルは ocaml コマンドを使いリンクされます。 例えば次の一連のコマンドは二つの翻訳単位 AuxMain からなるプログラムをビルドしています。

$ ocamlc -c Aux.mli                     # produces aux.cmi
$ ocamlc -c Aux.ml                      # produces aux.cmo
$ ocamlc -c Main.mli                    # produces main.cmi
$ ocamlc -c Main.ml                     # produces main.cmo
$ ocamlc -o theprogram Aux.cmo Main.cmo
    

得られたプログラムは、トップレベルで次のように入力された場合とまったく同様に振舞います。

module Aux: sig (* Aux.mli の内容*) end
          = struct (* Aux.ml の内容 *) end;;
module Main: sig (* Main.mli の内容 *) end
           = struct (* Main.ml の内容 *) end;;
    

特に、 MainAux を参照することが出来ます。 Main.ml 及び Main.mli に含まれる定義や宣言からは Aux.ident 記法により Aux.mli でエクスポートされた定義にアクセスすることが出来ます。

リンク時に ocaml コマンドに与えられる .cmo ファイルの順番は、その順番に各モジュールが定義されたものとして解釈されます。

そのため、この例では Aux が最初に現われているので Main から Aux を参照できますが、 Aux から Main は参照できません。

ここで注意が必要なのは、トップレベルのストラクチャのみが分割コンパイルされるファイルに対応させることが出来、ファンクタやモジュール型はそのままでは分割コンパイルされるファイルにすることが出来ないということです。 しかしながら、全てのモジュール類のオブジェクトはストラクチャの中におくことが出来るため、解決策としてはファンクタやモジュール型をストラクチャの中に置くことになります。 このストラクチャはファイルに対応させることができます。