次はSynonym Families。引数の型によって関連づけられた型を変えたいときに使います。C++でいうところの、特殊化されたテンプレートの中のtypedef*1とでも言えるでしょうか。
ありがちな例としては、コレクションクラスがあります。
class Collection a where type Elem a empty :: a add :: Elem a -> a -> a instance Collection [a] where type Elem [a] = a empty = [] add = (:) instance Collection (Seq a) where type Elem (Seq a) = a empty = Seq.empty add = (<|)
のような感じになります。typeをclassの定義の外でやる場合には、こんな感じ。
type family Elem a class Collection a where empty :: a add :: Elem a -> a -> a type instance Elem [a] = a instance Collection [a] where empty = [] add = (:)
*1 type_traitsとか
しかし、上のような例だと、Collectionを型クラスではなくて型構築子クラスにすれば、Synonym Familiesを使わなくても書けます。
class Collection c where empty :: c a add :: a -> c a -> c a instance Collection [] where empty = [] add = (:) instance Collection Seq where empty = Seq.empty add = (<|)
もちろん、Synonym Familiesを使えば、型構築子のkindが* -> *でコンテナの要素の型を渡さなくてはいけないという制限が外れるので自由度が高いです。たとえば、
data IntVector = IntVector [Int]
を上の(Synonym Familiesを使わない)Collectionのインスタンスにする事はできませんが、Synonym Familiesを使えば、
instance Collection IntVector where type Elem a = Int empty = IntVector [] add v (IntVector l) = IntVector $ v:l
のようにインスタンスにすることができます。
Synonym Familiesの別の良くある例はこんな感じのもの。
class Mul a b where type Result a b (*) :: a -> b -> Result a b instance Mul Int Int where type Result Int Int = Int (*) = (Prelude.*) instance Mul Int Double where type Result Int Double = Double (*) x y = fromIntegral Prelude.* y
これは、Functional Dependenciesを使ってこう書くことも出来ます。
class Mul a b c | a b -> c where (*) :: a -> b -> c instance Mul Int Int Int where (*) = (Prelude.*) instance Mul Int Double Double where (*) x y = fromIntegral x Prelude.* y
これを機械的に、Synonym Familiesを使った形に書き換えると、
class (Result a b ~ c) => Mul a b c where type Result a b (*) :: a -> b -> c
の様に書き換えられますが、GHC 6.10はまだ実装されていないそうです。