osiire’s blog

ふしぎなそふとやさん

既存モジュールを使って幽霊型を作る

幽霊型は型で特定のお約束を強制的に守るために役に立ちます。しかし、大抵は既存のモジュールの型の意味を後から使い分けたくなるのでそのためのテクニックも追加で必要になります。例えば次のような10進モジュールDecimalがあったとします。

module Decimal = struct
  type t = float (* ホントはNum.numを使った実装にするんだけど、ここでは省略 *)
  let of_float x = x
  let add a b = a +. b
  let sub a b = a -. b
  let compare a b = compare a b
end

このDecimal型を使って、アプリケーション内で消費税を扱いたいとします。そうすると、同じDecimal型でも税込金額とお約束している値とまだ未課税の金額とお約束している値とをaddしてしまうとまずいわけです。こういう時こそ幽霊な訳ですが、Decimalの演算をいちいち全部ラッピングしたくはない。そこで次のような魔術を使うと労力を削減できます。

module TaxedMoney : sig
  type 'a t constraint 'a = [< `Taxed | `NotTaxed ]
  val of_float : float -> [ `NotTaxed ] t
  val add : 'a t -> 'a t -> 'a t
  val sub : 'a t -> 'a t -> 'a t
  val compare : 'a t -> 'a t -> int
  val tax : [ `NotTaxed ] t -> [ `Taxed ] t
end = struct
  type 'a t = Decimal.t constraint 'a = [< `Taxed | `NotTaxed ]
  include (Decimal : (module type of Decimal) with type t := Decimal.t)
  let tax x = x *. 1.05
end

シグネチャーだけは再定義しなければなりませんが、少なくとも実装をラップする作業は避けられます。