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

时间:2016-07-28 14:59:52

标签: haskell typeclass functional-dependencies type-families

我玩过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与数据系列完成,但是你必须为每个包含的类型声明一个新的数据类型,例如使用{ {1}}。无论是那个还是一个内射型家庭,我认为在GHC 8之前是不可用的。完成了newtypeMultiParamTypeClasses

FunctionalDependencies

最后是两种类型和第三种类型之间的主观关系,例如python2或java样式除法函数,可以使用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 通过TypeFamilies完成。完成MultiParamTypeClassesMultiParamTypeClasses

FunctionalDependencies

我还应该补充的另一件事是,似乎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 = (/) FunctionalDependencies也更简洁(无论如何都是上面的例子),因为你只需要写一次类型,并且您不必提出一个虚拟类型名称,然后您必须为每个实例键入,就像使用MultiParamTypeClasses一样:

TypeFamilies

VS

instance FooBar LongTypeName LongerTypeName where
    FooBarResult LongTypeName LongerTypeName = LongestTypeName
    fooBar = someFunction

所以,除非我确信,否则我真的好像不应该为instance FooBar LongTypeName LongerTypeName LongestTypeName where fooBar = someFunction 而烦恼,只使用TypeFamiliesFunctionalDependencies。因为据我所知,它会使我的代码更简洁,更一致(更少关注的扩展),并且还会给我更多的灵活性,例如开放式关系或双向关系(后者可能是GHC的解决方案) 8)。

2 个答案:

答案 0 :(得分:3)

以下是TypeFamiliesMultiParamClasses FunctionalDependencies相比真正发光的示例。事实上,我要求您提出一个等效的MultiParamClasses解决方案,即使是使用FlexibleInstancesOverlappingInstance等的解决方案。

考虑类型级别替换的问题(我在QData.hs中的Quipper中遇到了此特定变体。基本上你想要做的是递归地将一种类型替换为另一种类型。例如,我希望能够

  • 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

偶然发现了一些对比和历史
  

类型族不再需要使用类型类   相关类型。后者并非严格必要,因为它可以   使用多参数类型类进行模拟,但它提供了更好的   在许多情况下的表示法。类型家庭也是如此;他们能   也可以通过多参数类型类进行模拟。但MPTC给出了一个   非常逻辑编程风格的做式计算;而类型   family(它们只是可以模拟匹配的类型函数)   arguments)就像函数式编程一样。

     

使用封闭式家庭   增加了类型类无法实现的额外强度。至   从类型类中获得相同的功能,我们需要添加闭合类型   类。哪个非常有用;这就是实例链   给你。

答案 1 :(得分:1)

函数依赖只影响约束求解的过程,而类型族引入了非句法类型相等的概念,通过强制表示为GHC的中间形式。这意味着类型家族与GADT的互动更好。有关功能依赖性如何失败的规范示例,请参阅this question

相关问题