【发布时间】:2012-03-16 06:13:08
【问题描述】:
我的一个朋友上周提出了一个看似无关紧要的 Scala 语言问题,但我没有很好的答案:是否有一种简单的方法可以声明属于某个常见类型类的事物的集合。当然,Scala 中没有一流的“类型类”概念,所以我们必须从特征和上下文边界(即隐式)的角度来考虑这一点。
具体来说,给定一些 trait T[_] 代表一个类型类,以及类型 A、B 和 C,在范围内有相应的隐式 T[A]、T[B] 和 T[C],我们要声明类似于List[T[a] forAll { type a }],我们可以将A、B 和C 的实例抛入其中而不受惩罚。这在 Scala 中当然不存在。 question last year 对此进行了更深入的讨论。
自然的后续问题是“Haskell 是如何做到的?”好吧,GHC 特别有一个类型系统扩展,称为impredicative polymorphism,在"Boxy Types" 论文中进行了描述。简而言之,给定一个类型类T,可以合法地构造一个列表[forall a. T a => a]。给定这种形式的声明,编译器会执行一些字典传递魔法,让我们在运行时保留与列表中每个值的类型相对应的类型类实例。
问题是,“字典传递魔法”听起来很像“vtables”。在像 Scala 这样的面向对象语言中,子类型化是一种比“Boxy Types”方法更简单、更自然的机制。如果我们的A、B 和C 都扩展了特征T,那么我们可以简单地声明List[T] 并感到高兴。同样,正如 Miles 在下面的评论中指出的那样,如果它们都扩展了特征 T1、T2 和 T3,那么我可以使用 List[T1 with T2 with T3] 作为不具格言的 Haskell [forall a. (T1 a, T2 a, T3 a) => a] 的等价物。
然而,与类型类相比,子类型的主要、众所周知的缺点是紧密耦合:我的A、B 和C 类型必须具有它们的T 行为。假设这是一个主要的交易破坏者,我不能使用子类型。所以 Scala 的中间立场是 pimps^H^H^H^H^Himplicit 转换:在隐式范围内给定一些 A => T、B => T 和 C => T,我可以再次非常高兴地用我的 @ 填充 List[T] 987654357@、B 和 C 值...
...直到我们想要List[T1 with T2 with T3]。此时,即使我们有隐式转换A => T1、A => T2 和A => T3,我们也不能将A 放入列表中。我们可以重构我们的隐式转换以提供A => T1 with T2 with T3,但我以前从未见过有人这样做,这似乎是另一种形式的紧密耦合。
好吧,我想我的问题最后是,我想,是之前在这里提出的几个问题的组合:"why avoid subtyping?" 和 "advantages of subtyping over typeclasses" ...和相同的?不知何故,隐含的转换是两人的秘密爱子?有人可以在 Scala 中阐明一个良好、干净的模式来表达多个边界(如上面的最后一个示例)吗?
【问题讨论】:
-
啊,抱歉,初读时错过了!
-
有一点我不清楚:如果
List[T]是列表[forall a. T a => a]的适当(上下文)翻译,那么为什么List[T1 with T2 with T3]不是列表@987654369 的适当翻译@? -
@MilesSabin 谢谢,这在我原来的帖子中并不清楚。我已经编辑澄清,如果我可以使用隐式转换,我实际上并不想从字面上扩展
T1、T2和T3。也感谢你有机会在 StackOverflow 上关于类型系统的问题中使用“秘密爱子”这个短语 :) -
我认为您对含意多态性和存在限定性感到困惑。类型
[forall a. Show a => a]表示所有类型的列表,例如Show a;[()]不满足这种类型,因为()是更具体的类型()。我不确定是否有任何值满足这种类型,但这不是你想要的;为了得到你想要的,你需要一个存在限定,它需要在它周围包装一个新的数据构造函数(尽管它没有必须成为一个 GADT)。 -
我相信您所问的并不是 Scala 的全部内容。尽管 Scala 使用函数式构造,但它的核心仍然是 OO,因此您应该使用子类型化。你的最后一个例子令人困惑。您需要 T1 AND T2 AND T3 类型的对象。 'A' 可以转换为 T1、T2 或 T3,但你能保证它可以转换为所有三个吗?我在这里找不到反例,但不难发现这样的结构可能会导致不健全的事情。因此,实际上需要对所有三个进行隐式转换以确保稳健性。只是我的两分钱。
标签: scala haskell functional-programming subtype impredicativetypes