使用特征时,我很难理解Scala中的线性化顺序:
class A { def foo() = "A" } trait B extends A { override def foo() = "B" + super.foo() } trait C extends B { override def foo() = "C" + super.foo() } trait D extends A { override def foo() = "D" + super.foo() } object LinearizationPlayground { def main(args: Array[String]) { var d = new A with D with C with B; println(d.foo) // CBDA???? } }
它打印CBDA
但我无法弄清楚为什么.如何确定特征的顺序?
谢谢
推理线性化的直观方法是参考构造顺序并可视化线性层次结构.
你可以这么想.首先构造基类; 但在能够构造基类之前,必须首先构造其超类/特征(这意味着构造从层次结构的顶部开始).对于层次结构中的每个类,混合特征从左到右构造,因为右侧的特征"稍后"添加,因此有机会"覆盖"先前的特征.然而,与类相似,为了构造特征,必须首先构建其基本特征(显而易见); 并且,相当合理的是,如果已经构建了特征(层次结构中的任何位置),则不再重建特征.现在,施工顺序与线性化相反.将"基础"特征/类视为线性层次结构中较高的特征,并且层次结构中较低的特征更接近于作为线性化主题的类/对象.线性化会影响特征中"超级"的解析方式:它将解析为最接近的基本特征(层次结构中较高).
从而:
var d = new A with D with C with B;
线性化A with D with C with B
是
(层次结构顶部)A(首先构造为基类)
线性化D.
A(以前不认为是A)
D(D延伸A)
C的线性化
A(以前不认为是A)
B(B延伸A)
C(C扩展B)
B的线性化
A(以前不认为是A)
B(以前不认为是B)
所以线性化是:ADBC.您可以将其视为线性层次结构,其中A是根(最高)并且首先构造,C是叶(最低)并且最后构造.由于C是最后构造的,这意味着可以覆盖"先前"成员.
给定这些直观的规则,d.foo
调用C.foo
返回一个"C",后面跟着super.foo()
它被解析B
(B
线性化中左边的特征,即更高/更高),返回一个"B",然后super.foo()
解析D
,它返回一个"D",然后super.foo()
解析A
,最后返回"A".所以你有"CBDA".
另一个例子,我准备了以下一个:
class X { print("X") } class A extends X { print("A") } trait H { print("H") } trait S extends H { print("S") } trait R { print("R") } trait T extends R with H { print("T") } class B extends A with T with S { print("B") } new B // X A R H T S B (the prints follow the construction order) // Linearization is the reverse of the construction order. // Note: the rightmost "H" wins (traits are not re-constructed) // lin(B) = B >> lin(S) >> lin(T) >> lin(A) // = B >> (S >> H) >> (T >> H >> R) >> (A >> X) // = B >> S >> T >> H >> R >> A >> X
接受的答案很精彩,但为了简化起见,我想以不同的方式尽力描述它.希望可以帮助一些人.
遇到线性化问题时,第一步是绘制类和特征的层次结构树.对于此特定示例,层次结构树将是这样的:
在第二个步骤是写下干扰目标问题的特征和类的所有线性化.在最后一步之前,你将需要它们.为此,您只需要编写到达根目录的路径.特征的线性化如下:
L(A) = A L(C) = C -> B -> A L(B) = B -> A L(D) = D -> A
第三步是编写问题的线性化.在这个具体问题中,我们正在计划解决线性化问题
var d = new A with D with C with B;
重要的是,有一个规则可以通过首先使用右先优先深度优先搜索来解析方法调用.换句话说,您应该从最右侧开始编写线性化.如下:L(B)>> L(C)>> L(D)>> L(A)
第四步是最简单的一步.只需将每个线性化从第二步替换为第三步.替换后,你会有这样的事情:
B -> A -> C -> B -> A -> D -> A -> A
最后但同样重要的是,您现在应该从左到右删除所有重复的类.应删除粗体字符: B - > A - > C - > B - > A - > D - > A - > A
你看,你有结果:C - > B - > D - > A 因此答案是CBDA.
我知道这不是个别深刻的概念描述,但可以作为我猜想的概念描述的补充.
Scala的特征堆栈,所以你可以通过一次添加一个来查看它们:
以new A
=> 开头foo = "A"
堆栈with D
=>foo = "DA"
堆叠的with C
堆栈with B
=>foo = "CBDA"
堆栈with B
什么也不做,因为B
已经堆叠在C
=>中foo = "CBDA"
这是一篇关于Scala如何解决钻石继承问题的博客文章.
scala解析超级调用的过程称为线性化. 在您的示例中,您将对象创建为
var d = new A with D with C with B;
所以指定scala引用文档这里调用super将被解析为
l(A) = A >> l(B) >> l(c) >> l(D) l(A) = A >> B >> l(A) >> l(C) >> l(D) l(A) = A >> B >> A >> C >> l(B) >> l(D) l(A) = A >> B >> A >> C >> B >> l(A) >> l(D) l(A) = A >> B >> A >> C >> B >> A >> l(D) l(A) = A >> B >> A >> C >> B >> A >> D >> l(A) l(A) = A >> B >> A >> C >> B >> A >> D >> A
现在从左边开始并删除重复的构造,其中右边将赢得一个
例如删除A,我们得到
l(A) = B >> C >> B >> D >> A
删除B,我们得到
l(A) = C >> B >> D >> A
这里我们没有任何重复的条目现在开始从C调用
CBDA
类中的super.foo C
会B
在B调用foo中调用foo in D
和foo ,依此类推.
PS这里l(A)是A的线性化