Data families can be used to build datatypes that have different implementations based on their type arguments.
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
In the above declaration, Nil :: List Char
, and UnitList :: Int -> List ()
Data families can also be associated with typeclasses. This is often useful for types with “helper objects”, which are required for generic typeclass methods but need to contain different information depending on the concrete instance. For instance, indexing locations in a list just requires a single number, whereas in a tree you need a number to indicate the path at each node:
class Container f where
data Location f
get :: Location f -> f a -> Maybe a
instance Container [] where
data Location [] = ListLoc Int
get (ListLoc i) xs
| i < length xs = Just $ xs!!i
| otherwise = Nothing
instance Container Tree where
data Location Tree = ThisNode | NodePath Int (Location Tree)
get ThisNode (Node x _) = Just x
get (NodePath i path) (Node _ sfo) = get path =<< get i sfo