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

§ 11.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は図11.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 11.1. main.smlの例