类型族可以做什么,多参数类型 类 和功能依赖不能

What can type families do that multi param type classes and functional dependencies cannot

我玩过 TypeFamiliesFunctionalDependenciesMultiParamTypeClasses。在我看来,TypeFamilies 似乎没有在其他两个上添加任何具体功能。 (但反之则不然)。但我知道字体家族很受欢迎,所以我觉得我错过了一些东西:

"open" 类型之间的关系,例如转换函数,TypeFamilies 似乎不可能。完成 MultiParamTypeClasses:

class Convert a b 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

类型之间的满射关系,例如一种类型安全的伪鸭子类型机制,通常使用标准类型族来完成。完成 MultiParamTypeClassesFunctionalDependencies:

class HasLength a b | 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)

类型之间的双射关系,例如对于未装箱的容器,这可以通过 TypeFamilies 与数据族来完成,尽管您必须为每个包含的类型声明一个新的数据类型,例如一个newtype。要么是那个,要么是单射类型族,我认为在 GHC 8 之前不可用。使用 MultiParamTypeClassesFunctionalDependencies:

完成
class Unboxed a b | 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

最后是两种类型和第三种类型之间的满射关系,例如python2或java式除法函数,可以用TypeFamilies也可以用MultiParamTypeClasses。完成 MultiParamTypeClassesFunctionalDependencies:

class Divide a b c | a b -> 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 = (/)

我还应该补充的另一件事是 FunctionalDependenciesMultiParamTypeClasses 似乎也更加简洁(无论如何对于上面的示例),因为您只需要编写类型一次,并且您不必想出一个虚拟类型名称,然后您必须像使用 TypeFamilies:

一样为每个实例键入该名称
instance FooBar LongTypeName LongerTypeName where
    FooBarResult LongTypeName LongerTypeName = LongestTypeName
    fooBar = someFunction

对比:

instance FooBar LongTypeName LongerTypeName LongestTypeName where
    fooBar = someFunction

所以除非我确信否则我真的不应该理会 TypeFamilies 而只使用 FunctionalDependenciesMultiParamTypeClasses。因为据我所知,它将使我的代码更简洁、更一致(需要关心的扩展更少),并且还会给我更多的灵活性,例如开放类型关系或双射关系(后者可能是 GHC 的求解器) 8).

这里有一个例子,说明 TypeFamiliesMultiParamClassesFunctionalDependencies 相比真正闪耀的地方。事实上,我挑战你想出一个等效的 MultiParamClasses 解决方案,即使是使用 FlexibleInstancesOverlappingInstance

的解决方案

考虑类型级别替换的问题(我 运行 在 QuipperQData.hs 中跨越了这个的特定变体。本质上,您要做的是递归地将一种类型替换为另一种类型。例如,我希望能够

  • Int代替Either [Int] String中的Bool得到Either [Bool] String,
  • [Int]代替Either [Int] String中的Bool得到Either Bool String,
  • [Int]代替Either [Int] String中的[Bool]得到Either [Bool] String.

总而言之,我想要类型级替换的通常概念。对于封闭类型族,我可以为任何类型执行此操作(尽管我需要为每个更高种类的类型构造函数添加一行 - 我在 * -> * -> * -> * -> * 处停止)。

{-# LANGUAGE TypeFamilies #-}

-- Subsitute type `x` for type `y` in type `a`
type family Substitute x y a where
  Substitute x y x = y
  Substitute x y (k a b c d) = k (Substitute x y a) (Substitute x y b) (Substitute x y c) (Substitute x y d)
  Substitute x y (k a b c) = k (Substitute x y a) (Substitute x y b) (Substitute x y c)  
  Substitute x y (k a b) = k (Substitute x y a) (Substitute x y b)
  Substitute x y (k a) = k (Substitute x y a)
  Substitute x y a = a

然后尝试 ghci 我得到了想要的输出:

> :t undefined :: Substitute Int Bool (Either [Int] String)
undefined :: Either [Bool] [Char]
> :t undefined :: Substitute [Int] Bool (Either [Int] String)
undefined :: Either Bool [Char]
> :t undefined :: Substitute [Int] [Bool] (Either [Int] String)
undefined :: Either [Bool] [Char]

话虽如此,也许您应该问自己 为什么我使用 MultiParamClasses 而不是 TypeFamilies。在您上面给出的示例中,除了 Convert t运行 之外的所有示例都被指定为类型族(尽管每个实例都需要额外的一行用于 type 声明)。

然后,对于 Convert,我不认为定义这样的东西是个好主意。 Convert 的自然扩展是

这样的实例
instance (Convert a b, Convert b c) => Convert a c where
  convert = convert . convert

instance Convert a a where
  convert = id

它们对于 GHC 来说是不可解析的,因为它们写起来很优雅...

需要说明的是,我并不是说 MultiParamClasses 没有用处,只是说在可能的情况下您应该使用 TypeFamilies - 它们让您考虑类型级函数,而不仅仅是关系。

This old HaskellWiki page does an OK job of comparing the two.

编辑

我从 8 月偶然发现了更多对比和历史 blog

Type families grew out of the need to have type classes with associated types. The latter is not strictly necessary since it can be emulated with multi-parameter type classes, but it gives a much nicer notation in many cases. The same is true for type families; they can also be emulated by multi-parameter type classes. But MPTC gives a very logic programming style of doing type computation; whereas type families (which are just type functions that can pattern match on the arguments) is like functional programming.

Using closed type families adds some extra strength that cannot be achieved by type classes. To get the same power from type classes we would need to add closed type classes. Which would be quite useful; this is what instance chains gives you.

函数依赖只影响约束求解过程,而类型族引入了非句法类型相等的概念,在 GHC 的中间形式中用强制转换表示。这意味着类型族可以更好地与 GADT 交互。请参阅 了解函数依赖性如何在此处失败的典型示例。