Ch.8 SML#分割コンパイルシステム

§ 8.2. 分割コンパイル例

前節のシナリオに従い,乱数を使うおみくじプログラムを例に,分割コ ンパイルをして実行形式ファイルを作ってみましょう. システムを

  • random:乱数発生器

  • main:メインパート

に分割することにします. まず,このインターフェイスファイルを以下のように設計します.

  • random.smiの定義.

    structure Random =
    struct
         val intit : int * int -> unit
         val genrand : unit -> int
    end

    このインターフェイスファイルは,このインターフェイスファイルを実 装するソースファイルが,他のファイルは参照せずにRandom structureを提 供することを表しています.

  • main.smiの定義.

    _require "basis.smi"
    _require "./random.smi"

    このインターフェイスファイルは,SML#の基本ライブラリ"basis.smi"(The Standard ML Basis Library)とこのディレクトリにある random.smiを利用し,外部には何も提供しないことを意味しています.

このインターフェイスを使えば,main.smlrandom.smlを独 立に開発できます. main.smlは図8.1のように定義できます. このファイルは,random.smiを実装するソースファイルが存在し なくても,コンパイルしエラーがないか確認することができます.

$ smlsharp -c main.sml

-cはSML#コンパイラにコンパイルしオブジェクトファイ ルを生成を指示します. 対話型での使用と同様に型チェックをした後,エラーがなければ, オブジェクトファイルmain.oを作ります. インターフェイスファイルは,ファイル名の.sml.smiに変 えたものが自動的に使用されます. ソースコード冒頭に_interface filePath宣言を書くことにより インターフェースファイルを明示的に指定することもできます.

次に,random.smlを開発します. 高品質の乱数発生関数の開発は,数学的な知識と注意深いコーディング が要求されます. ここでは,これはスクラッチから開発するのではなく,既存のCでの実 装を使うことにします. 種々ある乱数発生アルゴリズムの中で,その品質と速度の両方の点から Mersenne Twisterが信頼できます. このアルゴリズムはCのソースファイルmt19937ar.cとして提供されています.

そこでこのファイルをダウンロードしましょう. インターネットでMersenne Twisterあるいはmt19937ar.cをサーチすれば簡単に見つけることができます. このファイルには以下の関数が定義されています.

void init_genrand(unsigned long s)
void init_by_array(unsigned long init_key[], int key_length)
unsigned long genrand_int32(void)
long genrand_int31(void)
double genrand_real1(void)
double genrand_real2(void)
double genrand_real3(void)
double genrand_res53(void)
int main(void)

この中のmainは,このアルゴリズムをテストするためのメイン関 数です. 我々は新たな実行形式プログラムを作成するので,main関数は, 我々のトップレベルファイルmain.smlをコンパイルしたオブジェクトファ イルに含まれているはずです. そこで,mt19937ar.cファイルの関数int main(void)の定 義をコメントアウトする必要があります. その他の関数は,SML#から利用できるライブラリ関数です. ここでは以下の2つを使うことにします.

  • void init_genrand(unsigned long s). シーズの長さを受け取りアルゴリズムを初期化する関数です. シーズsは非負な整数ならなんでも構いません.

  • long genrand_int31(void).初期化された後は,呼ばれる毎に31ビッ トの符号なし整数(32ビット非負整数)のランダムな列を返します.

そこで,これを利用して,random.smlを以下のように定義します.

structure Random =
struct
     val init = _import "init_genrand" : int -> unit
     val genrand = _import "genrand_int31" : unit -> int
end

このソースファイルも,以下のコマンドにより,このファイルだけで単 独にコンパイルできます.

$ smlsharp -c random.sml

このソースファイルの定義と並行して,(main関数をコメントア ウトした)Mersenne Twisterをコンパイルし,オブジェクトファイルを生 成しておきます.

$ gcc -c -o mt.o mt19937ar.c

以上ですべてのソースファイルがそれぞれコンパイルされ,オブジェク トファイルが作られたはずです. それらオブジェクトファイルは,トップレベルのインターフェイスファ イルと必要なオブジェクトファイルを指定することによって,実行形式ファイル が作成されます.

$ smlsharp main.smi mt.o

SML#コンパイラは,smiファイルを解析し,このファイル から参照されているsmiファイルを再帰的にたどり,対応するオブジェク トファイルのリストを作り,コマンドラインに指定されたC言語のオブジェクト ファイルと共に,システムのリンカーを起動し,実行形式ファイルを作成します.

fun main() =
     let
          fun getInt () =
               case TextIO.inputLine TextIO.stdIn of
                 NONE => 0
               | SOME s => (case Int.fromString s of NONE => 0 | SOME i => i)
          val seed = (print "好きな数を入力してください(0で終了です)."; getInt())
     in
          if seed = 0 then ()
          else
               let
                    val _ = Random.init seed;
                    val oracle = Random.genrand()
                    val message =
                         "あなたの運勢は," ^
                         (case oracle mod 4 of 0 => "大吉" | 1 => "小吉"| 2 => "吉" | 3 => "凶")
                         ^ "です.\n"
                    val message = print message
               in
                    main ()
               end
     end
val _ = main();
Figure 8.1. main.smlの例