【问题标题】:How are Scala's traits not really traits?Scala 的特征如何不是真正的特征?
【发布时间】:2014-02-12 03:36:50
【问题描述】:

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

编辑: 作为“特质”的定义,我一直指的是 Nathanael Schärli 的 dissertation and concept paper 介绍特质。大多数 mixin 和/或多重继承实现似乎缺少的一项关键功能是在导入方法时重命名方法以避免冲突/歧义的能力。 Scala 能做到吗?

【问题讨论】:

  • 必须定义“trait”与“mixin”。例如,Ruby“mixins”在实现中不像 Scala“traits”(我认为 Ruby“mixins”可能更接近提到的“真实特征”?本质上是“MRO 中的存根”;-),这可能会抛出论点 - 术语需要更好的定义:-)
  • 如果我们发现它们不是“真正的”特征,那么最好有人去更新维基百科。 en.wikipedia.org/wiki/Trait_(computer_science)
  • 感谢您的提示,pst,我已经编辑了我的帖子以澄清。
  • 对于“真正的”特征实现,请查看 Perl 的 Moose::Roles 或 Javascript 的 traits.js。另外,我提出了一个类似的feature for C#(在Java 中可能非常相似)。我也开始了implementing这个愿景。
  • Scharli's dissertation 提到并讨论了 Scala 的特征(第 123 页)作为“特别有趣”的改编。主要区别在于: - Scala 特征被建模为不封装状态的抽象类, - Scala 特征不仅可以组合,还可以继承 - Scala 特征支持泛型 - Scala 特征不支持别名和排除

标签: scala traits mixins


【解决方案1】:

我认为这可能与 Scala 中的内容有关,而不是原始 paper 中提出的内容。

我曾经也考虑过这个问题,抛开实现差异不谈,我得出的结论是 Scala 中的特征确实有一些不足之处。 Scala 让你组合但不排除方法的方式很奇怪。为了避免冲突,它从其他语言中借用了称为方法解析顺序(或 Scala 中的线性化)的东西。支持多重继承的语言有一个众所周知的问题,我将大胆地将 Scala 归类为该组的成员。问题是理解起来过于复杂和耗时。

Scala 的方法解析顺序是一头奇怪的野兽,它有自己的方法分派算法。它不是 Dylan 的 C3,它在 Python 中使用,有一些值得注意的problems,但有与之相关的所有问题。更糟糕的是,我可以通过调用其.mro() 方法来查找Python 对象的MRO。 Scala 中没有等价物。

我可以告诉你,每次我需要查找方法将被解析到哪里时,我都不太喜欢在脑海中运行 Scala MRO 算法。

【讨论】:

  • 关于你的最后一点:一个好的IDE可以帮助你,例如在IDEA中按住Ctrl并点击一个方法调用会跳转到方法的定义。
  • Scala 的线性化是明确定义的、确定性的并且不是那么难(后来的 trait 获胜)。最重要的是,它可以静态解析,即编译器可以为一个类“绑定”使用的实现。我不确定排除方法是否是一个好功能,因为您可以用它打破替换原则;我倾向于声称,如果你需要它,你的设计就是糟糕的。无论如何,您应该避免使用冲突的方法。
  • @Raphael 我同意你的看法。对于不依赖鸭式打字的语言来说,打破替换原则可能是个坏主意。在大多数情况下,只要您没有过于复杂的层次结构,Scala 中的多重继承就可以正常工作,但是当您这样做时,只是考虑方法将解析到哪里可能会引起一些严重的麻烦。更有理由阻止糟糕的设计。在 Scala 中似乎真的没有更好的方法来做到这一点。
  • 您总是可以编写一个 ScalaDoc,这将为相应文档中的每个方法生成线性化顺序。
  • 尼尔,这不符合我的意见。我认为您可以将特征用作接口,因为我认为即使对于接口,您也应该避免冲突。当然,如果您使用一种方法为两个接口实现相同的签名,则在语言级别上不会发生冲突,但是这些接口可能会为相同的签名制定不同的合同/规范!这样的冲突甚至不能被编译器检测到(模形式规范),因此是最危险的错误:如果你假设 Liskov 被尊重,你就歪了——默默地。
【解决方案2】:

mixin 和 trait 之间的一个关键区别是 mixin 有字段,而 trait 没有。从原始论文中解释,一个特点:

  • 提供实现行为的方法
  • 需要参数化提供的行为的方法
  • 不要指定或访问任何字段
  • 对称构成
  • 可以嵌套,相当于扁平化的特征

乍一看,第三点似乎在 Scala 实现中被破坏了。然而,trait 只能访问受隐式 getter 和 setter 保护的公共字段。该论文继续描述了这对于特征的实现是可以接受的。

您指出特征的一个关键特性是可以在导入方法时对其进行重命名。鉴于 JVM 的限制,这是不可能的。可以在此处找到对此的连贯讨论:http://scala-programming-language.1934581.n4.nabble.com/Trait-method-aliasing-td2322026.html,尤其是 David Pollak 的帖子。

最后,我对您的一般性问题的回答是“有点”。详细地说,虽然 Scala 特征不是本文定义的严格特征,但它们也不是严格的 mixin。无论哪种方式,最好像使用它们一样使用它们并遵守它们的设计原则。

  • 保持较小,以便重复使用它们。
  • 指定行为而不是状态。

【讨论】:

    【解决方案3】:

    不,Scala 不能在导入时重命名。

    我想知道这甚至会如何工作。如果trait T的方法m在对象o中重命名为m2,如果pT类型的参数,o已经通过了,p.m怎么解决吗?

    【讨论】:

    • 在 Squeak 中,如果您重命名导入,并且您的其他特征都没有具有相同签名的方法,那么您必须自己重新实现该方法。查看this blogpost 中的最后一个示例,了解典型用例。
    • @Neil Squeak 是动态类型的,所以它并不真正适用于这一点,这是一个静态类型问题。
    • 当然可以,但我认为您在任何允许您重命名方法的语言中都有类似的要求。
    猜你喜欢
    • 2015-05-26
    • 2019-05-11
    • 2018-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    相关资源
    最近更新 更多