2009-09-08 [長年日記]

[Haskell] Type Families (3)

次は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とか

[Haskell] Type Families (4)

しかし、上のような例だと、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

のようにインスタンスにすることができます。

[Haskell] Type Families (5)

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はまだ実装されていないそうです。

[]

トップ «前の日記(2009-08-31) 最新