10.m - 20.cm + 10.mm - 3.cm == 9780.mm
型クラスを利用したミニ DSL
Frege では、DSL (ドメイン特化言語、Domain Specific Languages) をアプリケーションの中に埋め込むことが簡単にできるようになっています。ここではそれを実現するために、言語機能のひとつである型クラスを利用します。
初期目標
今回 の DSL では、距離の単位系をモデル化、すなわち 10 メートルを 10.m
のように書き表せるようにし、それを用いて
のような計算ができるようにしたいと思います。m
、cm
や mm
は普段使用する距離の単位と同じ意味です。
Note
|
この例は Groovy in Action から拝借しました。 |
初期実装
距離を表現するにあたって最初に思いつく実装のアイデアは、Int
型を用いて 1
が 1 ミリメートルを表現するようにすることです。この方法では、型システムの恩恵をさほど受けられない代わりに、距離どうしを自由に計算することができます。なお、この先の記事で改良を加える予定です。
ではどうしたら 1.mm
が値 1
になるようにできるでしょうか?
「the-power-of-the-dot.adoc[ドット記法の威力](」ですでに見たとおり、1.mm
は単に XXX.mm 1
、つまり型もしくは型クラス XXX
に対するドット記法に値 1
を続けたものに過ぎません。実際には Int
型は関数 mm
を持ってはいないので、ここでは型クラスを使用する必要があります。
class (Integral a) => Millimeter a where
mm :: a -> a
cm :: a -> a
m :: a -> a
この定義は、次のように読むことができます。
任意の型 "a" (ただし Int
や Integer
のような整数型だとする) は、与えられた "a" 型の値に対してそれをミリメートルに換算した "a" 型の値を返す関数 mm
、cm
および m
を定義することで、クラス Millimeter
の型になることができる。
Note
|
このような "a" は 型変数 と呼ばれ、型 の代わりとして機能します。通常の変数と同様、小文字で書く必要があります。 |
以上により型クラスが定義されたため、あとは Int
をこのクラスのインスタンスにするだけです。
instance Millimeter Int where
mm i = i
cm i = i.mm * 10
m i = i.cm * 100
さて、これで簡単に距離の計算ができるようになりました。
main args = do
println $ 10.m - 20.cm + 10.mm - 3.cm == 9780.mm
考察
ここでは、Millimeter
型クラスの一種にすることによって、Int
型に新たな機能を付与しました。この変更は 非侵入的 で インクリメンタル、つまり既存のコードに手を入れる必要がまったくありませんでした。何かを破壊してしまう心配がないという意味でこれは重要なことです。
この先の記事では、今回見た初歩的な型クラスを発展させ、距離だけでなく時間や距離もきちんと扱えるように洗練させて、型安全なモデル化を行います。
参考文献
Dierk König |
Why functional programming really matters: incremental development |