osiire’s blog

ふしぎなそふとやさん

first-class moduleをちょっと身近に

来るversion 3.12ではfirst-class moduleが導入されるわけですが、この機能はmoduleの世界を結構広げてくれます。しかし、その文法がやや煩雑で、真面目に使おうとすると疲れてしまう事が危惧されます。そこで、first-class moduleをお手軽に使えるcamlp4拡張を作りました。https://forge.ocamlcore.org/scm/viewvc.php/trunk/src/camlp4/pa_fcm.ml?view=markup&root=amthing
導入する文法は次の6つです。

  • "struct ... end"を"{ ... }"と書けます。
  • "sig ... end"を"{ ... }"と書けます。
  • 関数の引数で"( module M : Sig )"とすると、シグネチャーSigのモジュールMを束縛できます。
  • ファンクターの定義時に引数をシグネチャ名の後に列挙できます。ファンクターの定義時にfunctorキーワードの後に引数が複数列挙できます。
  • シグネチャーが"Sig with type a = a..."のようなwith typeを伴うような場合、その部分を"Sig [ type = a ]"のようにカギ括弧で括って表現できます。
  • "new Sig { ... }"と書くと、" struct ... end : Sig"と同じになります。
  • (exp : Sig1 :> Sig2)でSig1からSig2へシグネチャーを変更できます。

では、試しにこの文法でプログラムを書いてみましょう。

module type SigT = { type t }   (* 中括弧でスッキリ *)
module T = { type t }         (* 中括弧でスッキリ *)

module type Animal = {
  val speak : unit -> string
}

(* 複数引数のファンクターも楽々定義 *)
module type AnimalF = functor ( T : SigT ) (T2 : SigT) -> {     
val speak : unit -> string
}

let show ( module A : Animal ) =  (* モジュール束縛が直感的! *)
  Printf.printf "%s\n" (A.speak ())

module Dog = { let speak () = "bow-wow" let other_func = 1 }
module Cat = new Animal { let speak () = "mew" } (* なんかインスタンス生成みたいでしょ? *)

let _ =
  show (module Dog : Animal);  (* これはcamlp4拡張しなくても可能 *)
  show (module new Animal { let speak () = "moo" } );  (* これが拡張で可能に。 *)

これでv3.12対策はばっちりですね!