据我了解,Scala 线性化函数算法是右优先深度优先搜索,然后消除结果列表中每个特征的最后一次出现。
这个算法有两个方面对我来说似乎相当随意:
- 为什么首先探索特征而不是类别而不是相反?
- 为什么从右到左而不是从左到右遍历特征?
这些任意的约定和其他约定一样好吗?或者这些设计决策背后有什么理由?
请注意,我的问题仅涉及算法的“水平”排序。
这些武断的约定和其他约定一样好吗?
不,这些不是任意的。线性化是以这种方式完成的,因为它是为您提供理智的方法解析顺序 (MRO)的唯一方法。
为什么首先探索特征而不是类别而不是相反?
这仅适用于给定的类定义,因为它扩展的特征出现在它扩展的类之前。这是因为超类/特征是从右到左探索的——这将我们带到你的第二点。
为什么从右到左而不是从左到右遍历特征?
这是一个非常简单的激励示例。
type MyType = T1 with T2
// Creating new instance using alias
new MyType with T3
// Creating new instance directly
new T1 with T2 with T3
我们希望上面示例中的两个实例具有相同的线性化。当使用类型别名时,我们在右侧“堆叠”附加特征,因此我们希望右侧的特征在 MRO 中具有最高优先级,因此在线性化中排在首位。
这是我想出的一个快速示例,用于说明特征和类的线性化:
class Base { override def toString = "Base" }
trait A { abstract override def toString = "A " + super.toString }
trait B { abstract override def toString = "B " + super.toString }
trait C { abstract override def toString = "C " + super.toString }
trait D { abstract override def toString = "D " + super.toString }
trait E { abstract override def toString = "E " + super.toString }
trait F { abstract override def toString = "F " + super.toString }
class X extends Base with A with B { override def toString = "X " + super.toString }
class Y extends X with C with D { override def toString = "Y " + super.toString }
class Z extends Y with E with F { override def toString = "Z " + super.toString }
new Z
// res0: Z = Z F E Y D C X B A Base
您可以从toString
输出中看到这里的线性化是Z F E Y D C X B A Base
,这正是我对给定层次结构所期望的。例如,由于Z
extendsY
混合了 trait C
,我们期望Y
的行为出现在 之前C
,但之后Z
以及它的混合E
和F
。
Scala 编程,第一版:特性的第 12 章的简单参考。(我认为这最近没有改变,所以第一版应该还是准确的。)