Data Familiesは、型パラメータに依って実装を変えたいときに使えます。C++のテンプレートでの特殊化みたいなものでしょうか*1。
{-# LANGUAGE TypeFamilies #-} import qualified Data.Sequence as Seq data family Vector a -- BoolのVectorはリストで表現 data instance Vector Bool = BoolVector [Bool] -- IntのVectorはSeqで表現 data instance Vector Int = IntVector (Seq.Seq Int) -- 関数はクラスにする必要があります class VectorElem a where add :: a -> Vector a -> Vector a first :: Vector a -> a instance VectorElem Bool where add v (BoolVector l) = BoolVector $ v:l first (BoolVector l) = head l instance VectorElem Int where add v (IntVector s) = IntVector $ v Seq.<| s first (IntVector s) = Seq.index s 0
はじめからクラスの中にdata宣言を書くことも出来ます。
class VectorElem a where data Vector a add :: a -> Vector a -> Vector a first :: Vector a -> a instance VectorElem Bool where data Vector Bool = BoolVector [Bool] add v (BoolVector l) = BoolVector $ v:l first (BoolVector l) = head l instance VectorElem Int where data Vector Int = IntVector (Seq.Seq Int) add v (IntVector s) = IntVector $ v Seq.<| s first (IntVector s) = Seq.index s 0
classを伴わないdata familyの宣言が実際にどういう場面で使われるのか良くわからないのですが、上の例のようなケースではclass宣言内に書いた方がわかりやすい気がします。
*1 もっともデフォルトの実装は持てませんけど
上の例だと、GADTを使っても同じような事ができます。
{-# LANGUAGE GADTs #-} import qualified Data.Sequence as Seq data Vector a where BoolVector :: [Bool] -> Vector Bool IntVector :: Seq Int -> Vector Int add :: a -> Vector a -> Vector a add v (BoolVector l) = BoolVector $ v:l add v (IntVector s) = IntVector $ v Seq.<| s
こっちの場合だと、関数は単なるパターンマッチで書くことができるので型クラスにする必要はありません。
Data Familiesはオープンなので他のモジュールで新しい型に適用する事ができますが、GADTの場合にはデータコンストラクタを変更しないと新しい型を追加する事ができません。
また、(たぶん)GADTの場合、引数に対象の型が無いと実装を別ける事ができませんが、Data Familiesの場合には可能です。
例えば、GADTでは、
empty :: Vector a
という関数は実装できませんが、Data Familiesの場合には、
class VectorElem a where data Vector a empty :: Vector a instance VectorElem Bool where data Vector Bool = BoolVector [Bool] empty = BoolVector [] instance VectorElem Int where data Vector Int = IntVector (Seq.Seq Int) empty = IntVector Seq.empty
のように定義することができます。