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