I played with TypeFamilies , FunctionalDependencies and MultiParamTypeClasses . And it seems to me that TypeFamilies does not add any specific features compared to the other two. (But not vice versa). But I know that type families really like it, so I feel like something is missing:
an βopenβ relationship between types, such as a conversion function, which is not possible with TypeFamilies . Done using MultiParamTypeClasses :
class Convert ab where convert :: a -> b instance Convert Foo Bar where convert = foo2Bar instance Convert Foo Baz where convert = foo2Baz instance Convert Bar Baz where convert = bar2Baz
Surprising communication between types, such as a type of safe type pseudo-duck print engine that would normally run with a standard type. Done using MultiParamTypeClasses and FunctionalDependencies :
class HasLength ab | a -> b where getLength :: a -> b instance HasLength [a] Int where getLength = length instance HasLength (Set a) Int where getLength = S.size instance HasLength Event DateDiff where getLength = dateDiff (start event) (end event)
A bijective relationship between types, for example, for an unboxed container that can be executed through TypeFamilies with a data family, although then you need to declare a new data type for each contained type, for example, with newtype , Either this, or with a family of injective types, which , it seems to me, not available until GHC 8. Done using MultiParamTypeClasses and FunctionalDependencies :
class Unboxed ab | a -> b, b -> a where toList :: a -> [b] fromList :: [b] -> a instance Unboxed FooVector Foo where toList = fooVector2List fromList = list2FooVector instance Unboxed BarVector Bar where toList = barVector2List fromList = list2BarVector
And finally, the surjective relationship between the two types and the third type, such as the python2 or java style split function, which can be done with TypeFamilies , also using MultiParamTypeClasses . Done using MultiParamTypeClasses and FunctionalDependencies :
class Divide abc | ab -> c where divide :: a -> b -> c instance Divide Int Int Int where divide = div instance Divide Int Double Double where divide = (/) . fromIntegral instance Divide Double Int Double where divide = (. fromIntegral) . (/) instance Divide Double Double Double where divide = (/)
Another thing I should add is that it seems that FunctionalDependencies and MultiParamTypeClasses also quite a bit compressed (for the examples above anyway), since you only need to write the type once, t the type name of the dummy type should appear which you must enter for each instance, for example using TypeFamilies :
instance FooBar LongTypeName LongerTypeName where FooBarResult LongTypeName LongerTypeName = LongestTypeName fooBar = someFunction
vs
instance FooBar LongTypeName LongerTypeName LongestTypeName where fooBar = someFunction
Therefore, if I am not convinced of the other, it really seems that I simply should not worry about TypeFamilies and use exclusively FunctionalDependencies and MultiParamTypeClasses . Since, as far as I can tell, this will make my code shorter, more consistent (one smaller extension to take care of), and also give me more flexibility, e.g. with open-type relationships or bijective relationships (maybe the latter is a GHC 8 solver) .