datatype replication declarations の紹介

SMLの地味な機能を紹介。簡単な使用例をあんまり見かけないのでマイナーな機能だと思います。(大学の授業ではやらないとは思う)

まともな説明はこれくらいか>SML/NJのドキュメント(Datatype replication declarations)

名前は似てますがSML#のリプリケーション宣言とはちょっと違いますので注意して下さい。


以下で使うコードはhttps://gist.github.com/eldesh/5426405にあります。

まず以下のようなstructure Sがあるとして、

structure S = struct
  datatype either = L | R
end

次のようなstructure F1とF2を定義します。

structure F1 = struct
  type either = S.either
  val vL = S.L
end
structure F2 = struct
  datatype either = datatype S.either (* datatype replication declarations *)
  val vL = S.L
end

F1.eitherとF2.eitherは単にS.either型のエイリアスなので、それぞれ同じ型です。
違いはコンストラクタにあります。F1にはF1のみを使ってeither型の値を構築する術がありません。コンストラクタはstructure Sの中で定義されているからです。(F1の定義の中でS.Lに言及することは(当然)出来る)
ですが、F2にはコンストラクタごとS.eitherの定義がコピーされているので、F2の中で定義されたかのようにS.either型のコンストラクタが使えます。


つまり以下のようになります。

# S.L;
val it = _ : S.either
# F1.L; (* F1にはコンストラクタが定義されていない *)
(interactive):4.0-4.3 Error: (name evaluation 190) unbound variable: F1.L
# F2.L; (* F2にはコンストラクタごと定義が複製されている *)
val it = _ : S.either (* 元と同じ型 *)

構築した型が同じことも確認しておきます。

# S.L = F1.vL;
val it = true : bool
# S.L = F2.vL;
val it = true : bool

同じ型の値なのでちゃんと比較出来ていますね。

多相型のreplication

上の例では単相型でしたが、対応する多相型の例も出しておきます。

まず以下のようなstructureがあるとして、

structure S = struct
  datatype ('a,'b) poly_either = Left of 'a | Right of 'b
end

次のようにstructure F1,F2を定義します。

structure F1 = struct
  type ('a,'b) poly_either = ('a,'b) S.poly_either
  val pvL = S.Left 5
end
structure F2 = struct
  datatype poly_either = datatype S.poly_either (* datatype replication declarations *)
  val pvL = S.Left 5
end

エイリアスの場合は型変数が必要になりますが、replicationを使う時は型変数の指定はあってはいけません。
…罠っぽいですね。


一応比較も載せておきましょう。

# S.Left 5 = F1.pvL;
val it = true : bool
# S.Left 6 = F2.pvL;
val it = false : bool

work around

datatype replication declarationsはSML'97で入った機能らしいので、それより古いコンパイラ(SML'90?)では使えなかったハズです。
イマドキそんなコンパイラが存在するのかはあやしいですが、簡単のために他の機能でどうにか代替したくなるかも知れません。その手段について検討します。


もしかしたらこんな機能使わずに以下のように書けばいいと思うかも知れません。

structure Fx = struct
  type either = S.either
  val L = S.L
  val R = S.R
end

これだとFx.LとFx.Rは単なる関数であってコンストラクタでは無くなってしまうのでパターンマッチに使用出来なくなります。恐らく混乱するのでやめた方が良いです。*1


ではこんなのはどうでしょう?

structure Fx = struct
  datatype either = L | R
end

全く同じ定義を繰り返しています。これは字面は同じですが異なる型となります。(S.either != Fx.either)
全く同じ定義で異なる型が欲しいときはありますが、今回のworkaroundとしては使えませんね。

structure Fx = struct
  open S
end

これだと型は上手く扱えますが、余計なシグネチャまで全て公開されてしまって嬉しくないですね。 あらかじめ open S.Type; みたいになっていれば良さそうですが…。そんな状況無いでしょう(^^;;)

datatype replication specification

上の例ではstructure内での定義で説明しましたが、同じような構文はsignature内でも使うことが出来ます。
これは構文上 datatype replication specification といいます。


使い方を簡単に挙げておきます。

signature SIG = sig
  datatype poly_either = datatype S.poly_either (* datatype replication specification *)
end
structure F1 (* :> SIG = ... これは出来ない *)
structure F2 :> SIG = ...

型の定義を使い回せるのでシグネチャで型の定義を公開したい場合は便利そうですね。
この例で分かる通り、F1は単にeither型の名前を持っているだけなのでSIGを満たしません。F2は(コピーした)コンストラクタを持っているのでSIGを満たすことが出来ます。

*1:多分エラーメッセージで大混乱する