型クラスを利用したミニ DSL

Frege では、DSL (ドメイン特化言語、Domain Specific Languages) をアプリケーションの中に埋め込むことが簡単にできるようになっています。ここではそれを実現するために、言語機能のひとつである型クラスを利用します。

初期目標

今回 の DSL では、距離の単位系をモデル化、すなわち 10 メートルを 10.m のように書き表せるようにし、それを用いて

10.m - 20.cm + 10.mm - 3.cm  == 9780.mm

のような計算ができるようにしたいと思います。mcmmm は普段使用する距離の単位と同じ意味です。

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 を持ってはいないので、ここでは型クラスを使用する必要があります。

整数型 "a" に対する Millimeter 型クラスの定義
class (Integral a) => Millimeter a  where
    mm :: a -> a
    cm :: a -> a
    m  :: a -> a

この定義は、次のように読むことができます。

任意の型 "a" (ただし IntInteger のような整数型だとする) は、与えられた "a" 型の値に対してそれをミリメートルに換算した "a" 型の値を返す関数 mmcm および m を定義することで、クラス Millimeter の型になることができる。

Note
このような "a" は 型変数 と呼ばれ、 の代わりとして機能します。通常の変数と同様、小文字で書く必要があります。

以上により型クラスが定義されたため、あとは Int をこのクラスのインスタンスにするだけです。

Int 型を Millimeter 型クラスのインスタンスにする
instance Millimeter Int where
    mm i = i
    cm i = i.mm * 10
    m  i = i.cm * 100

さて、これで簡単に距離の計算ができるようになりました。

埋め込まれたミニ DSL を使用する
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

results matching ""

    No results matching ""