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

§ 11.6. ファンクタのサポート

SML#の分割コンパイルシステムは,ファンクタもオブジェク トファイルに分割コンパイルし,他のコンパイル単位から_require宣言 を通じて利用することができます. ファンクタのインターフェイスファイルは,そのProvide宣言に以下のよ うに記述します.

functor id(signature) =
struct
  (* この部分はstructureのProvideと同一 *)
end

ここで,signatureはStandard ML構文規則に従うシグネチャ宣言です. インターフェイスと似ていますが,インターフェイスではなく,通常のシグ ネチャが書けます. 以下に二分探索木を実現するファンクタのインターフェイ スファイルの例をしめします.

_require "basis.smi"
functor BalancedBinaryTree
  (A:sig
      type key
      val comp : key * key -> order
    end
  ) =
struct
  type 'a binaryTree (= boxed)
  val empty : 'a binaryTree
  val isEmpty : 'a binaryTree -> bool
  val singleton : key * 'a -> 'a binaryTree
  val insert : 'a binaryTree * A.key * 'a -> 'a binaryTree
  val delete : 'a binaryTree * key -> 'a binaryTree
  val find : 'a binaryTree * A.key -> 'a option
end

ただし,この機構を利用するプログラマは,以下の点に留意する必要が あります.

  • functorはモジュール分割のための道具ではない. 分割コンパイルができないML系言語処理系では,モジュールの間の直接 の依存関係を断ち切る手段としてのファンクタの使用が推奨されることがありました. 例えば,

    A.smlファイル:

    structure A =
    struct
      ...
    end

    B.smlファイル:

    structure B =
    struct
      open A
      ...
    end

    と書くとB.smlファイルが別な実装ファイルA.smlファイルに直接依 存してしまいます. このB.smlをファンクタを使い以下のように書きなおせば 依存性は解消されます.

    B.smlファイル:

    functor B(A:sig ... end) =
    struct
      open A
      ...
    end

    この機能は,分割コンパイルの機能そのものです. 分割コンパイルとリンクの機能を完全にサポートしているSML# では,この目的のためにファンクタを使う必要はなく,このような使用は 避けるべきです.

  • ファンクタの利用にはコストがかかる. ファンクタは,関数などの値以外に型もパラメタとして受け取る能力があ ります. これが,ファンクタなしでは達成できないファンクタ本来の機能です. しかし,同時に型をパラメタとして受け取り,その型に応じた処理を行 うため,ファンクタ本体のコンパイルには,型が決まっているストラクチャに比べて コンパイルにもコンパイルされたオブジェクトコードにもオーバヘッドが生まれ ます. これは,高度な機能を使用する上で避けられないことです. ファンクタは,このオーバヘッドを意識して,コードすべき高度な機能です.

現在のSML#のファンクタの実装には以下の制限があります.

  • ファンクタの引数に型引数を持つ抽象型コンストラクタが含まれている 場合,その型コンストラクタにはboxed実装表現を持つ型のみを適用する ことができます. 例えば,SML#では以下の例はコンパイルエラーになります.

    # functor F(type 'a t) = struct end structure X = F(type 'a t = int); (interactive):2.17-2.34 Error: (name evaluation "440") Functor parameter restriction: t