Multi-parameter classを利用した強力なポリモーフィズム

Haskellでは型クラスという仕組みにより、ポリモーフィズムが実現されています。
例えば、Showというクラスの場合だと、以下の様にshowという名前の関数により異なる実装が呼び出されるようにできます。

> show 1
"1"
> show 1e3
"1000.0"
> show True
"True"

しかし、showがString型を返す関数であるという点は変わりません。Multi-parameter classを利用するともっと強力なポリモーフィズムを実現することができます。

ジェネリックな値変換関数

例題としてある型の値を他のある型の値に変換できるconvertという関数を定義してみます。
まずは以下の定義を見てください。

{-# OPTIONS_GHC -fglasgow-exts 
                -fallow-undecidable-instances
                -fallow-overlapping-instances
                -fallow-incoherent-instances
#-}

module Convertible where
import Data.ByteString.Char8 as B

data Type a
proxy :: Type a
proxy = undefined

class Convertible from to where
    convert   :: Type to -> from -> to

instance Convertible a a where
    convert _ = id

instance (Read a) => Convertible String a where
    convert _ = read

instance (Show a) => Convertible a String where
    convert _ = show

instance Convertible B.ByteString String where
    convert _ = B.unpack

instance Convertible String B.ByteString where
    convert _ = B.pack

ある型をある型に変換した場合は、変換元の型は引数からわかりますが、変換先の型も指定しないとどの実装にディスパッチすべきかが分かりません。
そこで通常はread関数などのように戻り値に対して直接型を指定します。

> (read "100")::Int
100
> (read "100")::Double
100.0

一方Convertibleの定義は「引数の一つとして型を渡す」という実装になっています。
以下の様になります。

*Convertible> convert (proxy::Type Int) "100"
100
*Convertible> convert (proxy::Type Double) "100"
100.0
*Convertible> convert (proxy::Type ByteString) "100"
"100"
*Convertible> convert (proxy::Type String) True
"True"

Typeの実装

上のTypeはStrongly typed heterogeneous collectionsという論文にあるProxyというものと全く同じものですが、Typeの方が絶対にわかりやすいと思ったのでこのようにしました。

なぜTypeとうデータを改めて用意するのかということですが、まず一つとして安全性が高まるということがあるのではないかと思います。
例えば、コンテナを表すクラスの場合は以下のようになると思いますが、value_typeの型がcon -> valだと普通の関数と何も変わらないので、思いもがけない関数合成ができてしまいバグの元になります。しかし、con -> Type valであればType 〜を受け取る関数にしか合成できないので、そういったバグが減るということがあります。

また、型指定を見ただけで役割が分かるといった可読性面でもメリットもあります。

Type型にはデータコンストラクタが無いので、直接Type型のデータを生成することはできません。そこで値が未定義であるproxyという値に型を与える事でType型のデータを生成します。

応用

class Container con val | con -> val where
    value_type :: con -> Type val
    value_type _ = undefined

    ... (コンテナ操作用の関数達)

instance Container [a] a where
    ...
*Convertible> let a = [1,2,3,4,5]
*Convertible> :t a
a :: [Integer]
*Convertible> :t value_type a
value_type a :: Type Integer
*Convertible> convert (value_type a) "100"
100