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
のように定義することができます。