Scala的特质为何不是真正的特质?

8

最近有人告诉我Scala的特质不是“真正的”特质,而实际上只是mixin。不幸的是,我没有机会问他为什么。有人知道他的意思吗?

编辑: 作为“特质”的定义,我一直在参考Nathanael Schärli介绍特质的论文和概念论文。大多数mixin和/或多重继承实现缺少的一个关键特性是在导入方法时重命名以避免冲突/歧义。Scala能做到吗?


1
需要定义“trait”和“mixin”的区别。例如,Ruby的“mixins”在实现上与Scala的“traits”不同(我认为Ruby的“mixins”可能更接近所提到的“真正的traits”?本质上是MRO中的“存根”;-),这可能会使论点失去平衡 - 需要更好地定义术语 :-) - user166390
如果我们发现它们不是“真正”的特征,那么最好有人去更新维基百科。http://en.wikipedia.org/wiki/Trait_(computer_science) - Synesso
感谢您的及时回复,pst。我已经编辑了我的帖子以澄清。 - Neil Traft
对于“真正”的特征实现,请查看Perl的Moose::Roles或Javascript的traits.js。此外,我提出了类似的C#功能(在Java中可能非常相似)。我还开始实现这个想法。 - Jordão
Scharli的论文提到并讨论了Scala的特质(第123页)作为一种“特别有趣”的适应。主要区别包括:
  • Scala特质被建模为不封装状态的抽象类,
  • Scala特质不仅可以组合,还可以继承
  • Scala特质支持泛型
  • Scala特质不支持别名和排除
- Edoardo Vacchi
3个回答

9
我认为这可能与Scala中的内容有关,而不是原始文件中提出的内容。
我曾经也思考过这个问题,除了实现上的差异,我得出的结论是,Scala中的特质确实有所欠缺。Scala让你组合方法而不是排除方法的方式很奇怪。为了避免冲突,它从其他语言中借用了一种叫做方法解析顺序(或者在Scala中称为线性化)的东西。对于支持多重继承的语言来说,这是一个众所周知的问题,我会大胆地将Scala归为这一类。问题在于它太复杂,耗费时间去理解。
Scala的方法解析顺序是一种奇怪的生物,它有自己的方法调度算法。它不是Dylan的C3,后者在Python中使用,有一些明显的问题,但也具有与之相关的所有问题。更糟糕的是,我可以通过调用Python对象的.mro()方法来查找其MRO。在Scala中没有相应的方法。
我可以告诉你,每次需要查找方法解析的位置时,我不太喜欢在脑海中运行Scala MRO算法。

2
Scala的线性化是明确定义的、确定性的,而且并不难(后来的特质胜出)。最重要的是,它可以在静态情况下解决,也就是编译器可以为一个类“绑定”所使用的实现。我不确定排除方法是否是一个好的特性,因为你可以通过它打破替换原则;无论如何,你应该避免有冲突的方法。如果你需要它,那么你的设计可能有问题。 - Raphael
1
@Raphael我同意你的观点。对于那些不依赖duck-typing的语言来说,违反替代原则可能是一个坏主意。就大部分而言,在Scala中进行多重继承是可以正常运作的,只要你不拥有过于复杂的层次结构,但当你这样做时,仅仅考虑方法解析到哪里就可能会引起一些严重的头痛。更加理由是要避免不良设计。在Scala中似乎真的没有更好的方式来处理它。 - Y.H Wong
3
您可以随时编写ScalaDoc,这将在各自的文档中为每个方法生成线性化顺序。 - Daniel C. Sobral
3
尼尔,这与我的观点不符。我认为您可以使用特征作为接口,因为即使对于接口,您也应该避免冲突。当然,如果您使用一个方法在两个接口中实现相同的签名,语言层面上就不会有冲突,但是这些接口可能会为相同的签名制定不同的合同/规范!这种冲突甚至无法被编译器检测到(除非是正式规范),因此它是最危险的错误类型:如果您认为Liskov原则得到了遵守,那么你是错误的 - 而且是静默的。 - Raphael
@Daniel 很好的评论!请注意,您甚至不必编写任何文档注释(据我所知),超类型线性化将始终显示。 - Raphael
显示剩余5条评论

2
一个重要的区别在于,mixin有字段而traits没有。从原始论文中解释,trait:
- 提供实现行为的方法 - 要求参数化提供的行为的方法 - 不指定或访问任何字段 - 对称组合 - 可以嵌套,相当于扁平化的traits
乍一看第三点似乎在Scala实现中被打破了。然而,traits只能访问公共字段,这些字段由隐式getter和setter保护。该论文继续描述了这对于traits的实现是可以接受的。
您指出traits的一个关键特性是导入时方法可以重命名。在JVM的限制下,这是不可能的。这里可以找到一致的讨论:http://scala-programming-language.1934581.n4.nabble.com/Trait-method-aliasing-td2322026.html,特别是David Pollak的帖子。
最后,对于您的一般问题,我的答案是“有点像”。具体而言,虽然Scala traits不严格符合论文中定义的traits,但它们也不严格属于mixins。无论哪种方式,最好将它们用作traits并遵循其设计原则。
- 保持小型,旨在重用它们。 - 指定行为而不是状态。

1

不,Scala 无法在导入时重命名。

我想知道这该怎么做。如果 trait T 的方法 m 在对象 o 中被重命名为 m2,那么如果 p 是类型为 T 的参数,并且通过它传递了 o,那么如何解析 p.m


在Squeak中,如果您重命名一个导入,并且您的其他特征都没有使用相同签名的方法,则必须自己重新实现该方法。请查看此博客文章中的最后一个示例,以了解典型用例。 - Neil Traft
@Neil Squeak是动态类型的,所以它并不适用于这个问题,这是一个静态类型的问题。 - Daniel C. Sobral
当然可以,但我认为在任何一种允许您像那样重命名方法的语言中,您都会有类似的要求。 - Neil Traft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接