OCamlで幽霊型を応用してみました。 お金型に税込みかどうかの区別を付けて、二重に税金を計算することを 防ぐことができます。
まずシグネチャーを定義します。 ここでは、裸の金額と税込みの金額を区別した型を定義して、 tax関数は裸の金額にしか適用できないよう制限します。
# module type MoneySig = sig type 'a money constraint 'a = [< `Naked | `Taxed ] val make : float -> [`Naked] money val add : 'a money -> 'a money -> 'a money val mul : 'a money -> 'a money -> 'a money val tax : [`Naked] money -> [`Taxed] money val show : 'a money -> string end ;; module type MoneySig = sig type 'a money constraint 'a = [< `Naked | `Taxed ] val make : float -> [ `Naked ] money val add : ([< `Naked | `Taxed ] as 'a) money -> 'a money -> 'a money val mul : ([< `Naked | `Taxed ] as 'a) money -> 'a money -> 'a money val tax : [ `Naked ] money -> [ `Taxed ] money val show : [< `Naked | `Taxed ] money -> string end
先ほどのシグネチャーを適用したモジュールを作ります。 実体をfloatとしています。
# module Money : MoneySig = struct type 'a money = float constraint 'a = [< `Naked | `Taxed ] let make x = x let add = (+.) let mul = ( *.) let tax x = x *. 1.05 let show = string_of_float end;; module Money : MoneySig
では、Moneyモジュールを使ってみましょう。
# let juice = Money.make 120.0;; val juice : [ `Naked ] Money.money = <abstr>
ジュースは120円、税抜きとします
# Money.tax (Money.tax juice);; This expression has type [ `Taxed ] Money.money but is here used with type [ `Naked ] Money.money These two variant types have no intersection
二重に課税すると型エラーです。
# Money.add juice juice;; - : [ `Naked ] Money.money = <abstr> # let pay = Money.tax juice;; val pay : [ `Taxed ] Money.money = <abstr> # print_string (Money.show pay);; 126.- : unit = ()
通常の使い方はこんな感じ。
# Money.add juice pay;; Characters 16-19: Money.add juice pay;; ^^^ This expression has type [ `Taxed ] Money.money but is here used with type [ `Naked ] Money.money These two variant types have no intersection #
裸の金額と税込み金額をごちゃ混ぜにして足し算しようとしても、きちんとエラーになります