我玩过TypeFamiliesFunctionalDependenciesMultiParamTypeClasses。在我看来,TypeFamilies并未在其他两个之上添加任何具体功能。 (但反之亦然)。但是我知道类型家庭非常喜欢,所以我觉得我缺少一些东西:

类型之间的“开放”关系,例如转换函数,对于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。要么使用这种类型,要么使用注入(inject)型家族,我认为在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

vs:
instance FooBar LongTypeName LongerTypeName LongestTypeName where
    fooBar = someFunction

因此,除非我坚信,否则似乎真的不应该只用TypeFamilies而是只使用FunctionalDependenciesMultiParamTypeClasses。因为据我所知,它将使我的代码更简洁,更一致(无需过多扩展),并且还为我提供了更大的灵活性,例如开放类型关系或双射关系(后者可能是GHC的求解器) 8)。

最佳答案

这是TypeFamiliesMultiParamClassesFunctionalDependencies相比真正发光的一个示例。实际上,我挑战您提出一种等效的MultiParamClasses解决方案,甚至是使用FlexibleInstancesOverlappingInstance等的解决方案。

考虑类型级别替换的问题(我在QData.hsQuipper中遇到了这种情况的特定变体)。本质上,您想要做的是将一种类型递归替换为另一种类型。例如,我希望能够

  • Int替换Bool中的Either [Int] String并获得Either [Bool] String
  • [Int]替换Bool中的Either [Int] String并获得Either Bool String
  • [Int]替换[Bool]中的Either [Int] String并获得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之外的所有示例都转换为类型族(尽管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

    编辑

    我从八月blog偶然发现了一些对比和历史

    关于haskell - 类型族可以做什么,而多参数类型类和功能依赖项不能做到,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38640052/

    10-12 12:44
    查看更多