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対策はばっちりですね!