fstclassmodule遊びちょびまとめ

纏めるのが面倒臭かったので、とぎゃたーを使いました。まぁついーとを纏めるならここに書くよりもコレを使った方が良い筈です楽ですし。

http://togetter.com/li/67542

大まかに話をまとめると、

一番はじめはこのエントリ http://d.hatena.ne.jp/ranha/20101107/1289129831 で、やりたかった事はモジュールに関して多相に振る舞う"関数"を実装したかったというわけでした。
まぁ例として、Map.Sをimplementしたブツを受け取って〜という関数を設定したのですが。

この関数を実装するヒントとして、
http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#toc81
リファレンスマニュアルの

       let sort (type s) set l =
          let module Set = (val set : Set.S with type elt = s) in
          Set.elements (List.fold_right Set.add l Set.empty)

これを使おうと考えたわけです。
このsort関数の実装には、OCaml 3.12で入った機能である"Explicit naming of type variables"と"First-class modules"を使っています。
前者はGHC的に言うならば、ScopedTypeVariablesといった所なのでしょうか。
Explicit naming of type variablesを使うのは、局所的に定義したモジュールにくっつく型をそのスコープから持ち出す事は出来ないので、外から見えるようにしてあげる為だとぼくは理解しています。

では、Explicit naming of type variablesを今回の例に沿って使おうとするならば、それは間違いなく

let is_empty_ (type t) modl =
  let module Modl = (val modl : Map.S with type 'a t = t) in (* ここ !! *)
  let aux (map : t) = Modl.is_empty map in
    aux

このコードにおける(* ここ !! *)の部分に成る筈なのです。

ここで、とぎゃたーの発言をみると、ぼくとにはさんが「型変数を取る型コンストラクタに対するpackage-type-constraint」はそもそも書けないんでないのか、という話を書いています。しかし実はこれは誤解で…。

というのも、

(* 後述する http://caml.inria.fr/mantis/view.php?id=5140 に書いてある例 *)

module type S = sig
  type 'a t
  val f : 'a t -> unit
end

module S_impl = ((struct
		    type 'a t = X
		    let f _ = ()
		  end)
		   : S)
  
module type S' = S with type 'a t = int (* 書ける!! *)

module S'_impl = ((struct
		     type 'a t = int
		     let f i =
		       print_endline (string_of_int i);
		       ()
		   end)
		    : S')

let _ = S'_impl.f 42

として出来まするよね・・・という話になります。

調べてみると、
http://caml.inria.fr/mantis/view.php?id=5078
http://caml.inria.fr/mantis/view.php?id=5140
という2つのトピックが建っていました。
で、fstclassmod_parametrizedブランチ( http://caml.inria.fr/svn/ocaml/branches/fstclassmod_parametrized/ )から引っ張って来て使えないか調べてみようという話になりました。

いまいさんがイデオンhttp://ideone.com/36Fhf これを投げた所で話は大体終わっています。

イデオンにあるコードは、ポイントとしては

  1. 関数は実装する事が出来る (ext関数は実装されています)
  2. ただし、extの型付けで、特に(module S with type t = 'b)というモジュールを取ろうとする為に、どうしてこうなった

という話になっているという事です。

本題であったis_empty_も同様に以下の通りに実装する事が出来ます。

let is_empty_ (type x) modl =
  let module Modl = (val modl : Map.S with type 'a t = x) in
  let aux (map : x) = Modl.is_empty map in
    aux

(*
let _ =
  let module SMap = Map.Make(String) in
  let a = is_empty_ (module SMap : Map.S) (SMap.empty) in
    print_endline (string_of_bool a)
*)

is_empty_自体は実装出来るのですが、所がこのis_empty_の型というのは

type:   (module Map.S with type t = 'a) -> 'a -> bool

これは不味いです。コメントアウト部のような本来望む使い方が出来ません。

Error: This expression has type (module Map.S)
       but an expression was expected of type (module Map.S with type t = 'a)

どうやって型合わせれば良いんだ…。最悪、moduleをObj.magicして渡せば良いかもしれませんが…。

implicit-unpacking的な書き方が出来るように成れば

let is_empty_ (type x) (module Modl : Map.S with type 'a t = x) =
  let aux (map : x) = Modl.is_empty map in
    aux

とかもうちょっとまともに型を与えられるんじゃないかとも思うんですが…。

もしくは、"Explicit naming of type variables"が、"type variables"を取るのでは無く、いまいさんが書いているように( cf . やはり fun (type 'a s) ... -> のように書けないと厳しいのかな )、型コンストラクタ一般で抽象出来る方が良いのかなぁというのがあってしかしそれは…という感じです。