module type of

3.12.0出ましたね!MacPortsになかなか来ないので、ローカルでビルドして移行中です。
さて今回新機能目白押しです。その中でも個人的に一番ありがたいのがmodule type ofです。面倒くさいsig宣言のコピー&ペーストをかなり削減できます。
頻繁に弄ってる最中ですと、2箇所直さないといけないのが1箇所で済むようになるのは本当に嬉しいです。やったー。

で、ままならないのがファンクタ。

module type P = sig type t end
module type T = sig ...MANY-DECLARATIONS... end
module F (A: P) = struct ...MANY-DECLARATIONS... end

今まで...MANY-DECLARATIONS...の部分を2回書いていたのを、↓のように直したいわけです。

module type P = sig type t end
module F (A: P) = struct ...MANY-DECLARATIONS... end
module type T = module type of F(???)

どうすればスマートかなあ、とあれこれやってみた話。
ここで、???に既存のモジュールDummyを充てがってしまうと、A.tがDummy.tに固定されて元と同じにならず。

module type P = sig type t end
module F (A: P) = struct type t = A.t end (* 例ですので以降tだけ *)
module Dummy = struct type t end
module type T = module type of F(Dummy)

↑をocamlc -iすると↓になってしまいます。

% ocamlc -i test.ml
module type P = sig type t end
module F : functor (A : P) -> sig type t = A.t end
module Dummy : sig type t end
module type T = sig type t = Dummy.t end (* ここからDummy.tを消したい *)

そこで、これまた新機能のdestructive substitutionを試してみる。

module type P = sig type t end
module F (A: P) = struct type t = A.t end
module Dummy = struct type t end
module type T = sig
  type t
  include module type of F(Dummy) with type t := t
end

……撃沈しました。

% ocamlc -i test.ml
File "test.ml", line 6, characters 10-49:
Error: In this `with' constraint, the new definition of t
       does not match its original definition in the constrained signature:
       Type declarations do not match:
         type t = t
       is not included in
         type t = Dummy.t

色々試した部分を省くと、引数モジュールをインラインで与えればOKでした。tを抽象型にすることで他の型に縛られないシグネチャが得られます。

module type P = sig type t end
module F (A: P) = struct type t = A.t end
module type T = module type of F(struct type t end)
% ocamlc -i test.ml
module type P = sig type t end
module F : functor (A : P) -> sig type t = A.t end
module type T = sig type t end

実際には引数型Pもtype tだけということはなく、関数や値があったりするわけです。
ダミーの引数モジュールの方は、型さえ満足させればいいので、楽勝……?

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module type T = module type of F(struct
  type t
  let compare _ _ = 0 (* 楽勝 *)
  let create _ = ... (* どうしよう…… *)
  let empty = ... (* 同上 *)
end)

tの値を作らない限りはいいのですが、tを返す関数を書こうとして詰まりました。ここでtに具体的な型を与えるわけにはいきません。
大丈夫、こういう時のためのObj.magic!実行されてしまうとクラッシュしますが、実際には呼ばれないから関係なし!(またかよ……)

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module type T = module type of F(struct
  type t
  let compare _ _ = 0
  let create _ = Obj.magic 0
  let empty = Obj.magic 0
end)
% ocamlc -i test.ml
module type P =
  sig
    type t
    val compare : t -> t -> int
    val create : unit -> t
    val empty : t
  end
module F : functor (A : P) -> sig type t = A.t end
module type T = sig type t end

でもやはり不安なので、もう一捻りして、ダミー関数が実際に呼ばれないことをリンカに保証させましょう。

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module type T = module type of F(struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ" (* これはひどい *)
  type t
  let compare _ _ = 0
  let create _ = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end)
% ocamlopt test.ml # ちゃんとリンクされます

module type ofの中で何を宣言しても、機械語には落ちない。こういう所がしっかりしてて素敵です。
実はダミーの引数も不要です。'aって便利。もちろんObj.magicのままでも同じです。

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module type T = module type of F(struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  type t
  let compare = ultrasuperdeluxmagic ()
  let create = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end)

さて、実際にはPの方も結構ボリュームがあったりして、Fの内容をコピー&ペーストするよりも、ダミーを作るほうが苦痛だったりします。
特にFのようなファンクタが複数あったりすると……。

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module type FT = module type of F(struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  type t
  let compare = ultrasuperdeluxmagic ()
  let create = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end)
module G (A: P) = struct type t = A.t end
module type GT = module type of F(struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  type t
  let compare = ultrasuperdeluxmagic ()
  let create = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end)
module H (A: P) = struct type t = A.t end
module type HT = module type of F(struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  type t
  let compare = ultrasuperdeluxmagic ()
  let create = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end)

幸いにも'aのおかげでcompareやcreateのダミー実装は型tに縛られてません。ここだけでもincludeできないでしょうか。

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module Dummy = struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  let compare = ultrasuperdeluxmagic ()
  let create = ultrasuperdeluxmagic ()
  let empty = ultrasuperdeluxmagic ()
end;;
module type FT = module type of F(struct
  type t
  include Dummy
end)
module G (A: P) = struct type t = A.t end
module type GT = module type of F(struct
  type t
  include Dummy
end)
module H (A: P) = struct type t = A.t end
module type HT = module type of F(struct
  type t
  include Dummy
end)
% ocamlopt test.ml
Undefined symbols:
  "_$e3$81$aa$e3$81$84$e3$82$88", referenced from:
      _camlTest__6 in test.o
      _$e3$81$aa$e3$81$84$e3$82$88$non_lazy_ptr in test.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
File "caml_startup", line 1, characters 0-1:
Error: Error during linking

ultrasuperdeluxmagicも、module type ofの外に出してしまうとこの様。当初の意図通りリンカによるチェックが機能してるとも言いますが。
それでもObj.magicなら、Obj.magicならなんとかしてくれる……!!

……結果だけ書きますと……いまいち挙動をよくわかってないのですが、Obj.magic 0版は、動くことは動くのですが、対話環境にロードしたときとかなんか怪しかったです。
まあ、もしObj.magic 0の代わりにassert falseを使うとして考えてみればわからないこともないです……。

module Dummy = struct
  let compare _ _ = assert false
  let create _ = assert false
  let empty = assert false
end;;

emptyのところでassert falseが実行されてしまってますよね、これ!実行されないことが前提の'aなのに!
assert falseやultrasuperdeluxmagicで発生するエラーをObj.magicは抑え込みますが、それで怪しいのもまあしょうがない。

というわけでどうするか……Dummyの初期化のための実行を回避できればいいので、Dummyもファンクタにすれば……。
怪しい動作を回避するのが目的ですので、一応安全のためultrasuperdeluxmagicを使います。

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module Dummy (A: sig end) = struct
  external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  let compare = assert false
  let create = assert false
  let empty = assert false
end;;
module type FT = module type of F(struct
  type t
  include Dummy (struct end)
end)
module G (A: P) = struct type t = A.t end
module type GT = module type of F(struct
  type t
  include Dummy (struct end)
end)
module H (A: P) = struct type t = A.t end
module type HT = module type of F(struct
  type t
  include Dummy (struct end)
end)
% ocamlopt test.ml 
Undefined symbols:
  "_$e3$81$aa$e3$81$84$e3$82$88", referenced from:
      _camlTest__10 in test.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
File "caml_startup", line 1, characters 0-1:
Error: Error during linking

ああ……ファンクタといってもコードは生成されてしまうのですね……。module type ofの外ですから仕方ないですが……。やはりultrasuperdeluxmagicはmodule type ofの中で定義しないといけません。
というわけでこれが最終コードだ!

module type P = sig
  type t
  val compare: t -> t -> int
  val create: unit -> t
  val empty: t
end
module F (A: P) = struct type t = A.t end
module Dummy (A: sig val ultrasuperdeluxmagic: unit -> 'a end) = struct
  let compare = A.ultrasuperdeluxmagic ()
  let create = A.ultrasuperdeluxmagic ()
  let empty = A.ultrasuperdeluxmagic ()
end;;
module type FT = module type of F(struct
  type t
  include Dummy (struct
    external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  end)
end)
module G (A: P) = struct type t = A.t end
module type GT = module type of F(struct
  type t
  include Dummy (struct
    external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  end)
end)
module H (A: P) = struct type t = A.t end
module type HT = module type of F(struct
  type t
  include Dummy (struct
    external ultrasuperdeluxmagic: unit -> 'a = "ないよ"
  end)
end)
% ocamlopt test.ml # ちゃんとリンクされます