【问题标题】:What can type families do that multi param type classes and functional dependencies cannot类型族可以做什么多参数类型类和功能依赖不能
【发布时间】:2016-12-03 01:31:13
【问题描述】:

我玩过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 .无论是那个还是单射类型族,我认为在 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).

【问题讨论】:

  • 类型族的性能通常比fundeps好得多——尤其是在实现类型函数时。
  • 还有一个可读性问题。通过 FunDeps 表示的 type F a = G (H (G a) (G a)) 的等价物需要几个约束,涉及一些辅助类型变量。当我读到这样的约束时,我发现自己试图以一种函数的形式来表达它们。也许这是因为我更习惯于阅读函数式代码而不是 prolog。
  • @ErikR 为什么会这样?这似乎非常奇怪,因为两个选项都可行的示例在类型分辨率方面看起来完全一样,而对我来说不是。这只是FunctionalDependenciesMultiParamTypeClasses 或其交互的当前实现中的一个缺陷吗?或者它是更基本的东西?我真的希望是前者,因为出于性能原因,不必在同一件事的一半时间使用不同且看似更冗长的语法会很好。
  • @chi 你介意再深入一点你的意思吗?我有点困惑,因为通常我看到type F atype F a :: * 没有= 或者我看到type F G = ...F 之后立即有一个具体类型,为什么在F 之后有一个类型变量和一个=。我对这些东西有点陌生。
  • @chi 如果您正在谈论在TypeFamilies 之外使用type,这似乎是使您的示例正常工作的唯一方法,那么我看不出TypeFamilies 之间有什么不同和FunctionalDependencies,如果有任何区别,TypeSynonymInstances会缓解吗?

标签: haskell typeclass functional-dependencies type-families


【解决方案1】:

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

考虑类型级替换的问题(我在QuipperQData.hs 中遇到了这个的特定变体)。本质上,您想要做的是递归地用一种类型替换另一种类型。例如,我希望能够

  • Either [Int] String 替换Int 中的Bool 并得到Either [Bool] String
  • [Int] 替换为Either [Int] String 中的Bool 并得到Either Bool String
  • [Bool] 替换Either [Int] String 中的[Int] 并得到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.

编辑

我从 augustss blog 偶然发现了一些对比鲜明的历史

类型族源于对类型类的需求 关联类型。后者不是绝对必要的,因为它可以 用多参数类型类模拟,但它提供了一个更好的 很多情况下的符号。类型族也是如此;他们能 也可以通过多参数类型类来模拟。但是 MPTC 给出了一个 进行类型计算的非常逻辑的编程风格;而类型 族(它们只是类型函数,可以在 arguments) 就像函数式编程。

使用封闭类型族 增加了一些类型类无法实现的额外强度。到 从类型类中获得相同的能力,我们需要添加封闭类型 类。这将非常有用;这就是实例链 给你。

【讨论】:

  • 好吧,我想我终于或多或少明白了。我是否正确地认为,如果您所做的只是像我在原始帖子中所做的那样指定类型关系并且您没有以任何特别非标准的方式使用它们,那么FunctionalDependenciesMultiParamTypeClasses 工作得很好。但是,如果您尝试超越这一点并进行真正的类型级编程,那么使用TypeFamilies 会更好。在灵活性和一般优雅方面。因此,一般而言,您应该坚持使用TypeFamilies 以保持优雅和灵活性。
  • @semicolon 是的,这是一个很好的总结。 TypeFamilies MultiParamTypeClasses 更新,IFRC 旨在使类型级编程更像值类型编程(而不是逻辑等价)。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-08
    相关资源
    最近更新 更多