scala 线性回归
这是Scala特性实现和互操作性的延续。 第一部分:基础知识 。 可怕的钻石问题可以通过使用Scala特征和称为线性化的过程来缓解。 请看以下示例:
trait Base {
def msg = "Base"
}
trait Foo extends Base {
abstract override def msg = "Foo -> " + super.msg
}
trait Bar extends Base {
abstract override def msg = "Bar -> " + super.msg
}
trait Buzz extends Base {
abstract override def msg = "Buzz -> " + super.msg
}
class Riddle extends Base with Foo with Bar with Buzz {
override def msg = "Riddle -> " + super.msg
}
现在让我问一个问题: (new Riddle).msg
的输出是什么?
-
Riddle -> Base
-
Riddle -> Buzz -> Base
-
Riddle -> Foo -> Base
-
Riddle -> Buzz -> Bar -> Foo -> Base
不是(1),因为Base.msg
被我们扩展的所有特征所覆盖,所以这并不奇怪。 但这不是(2)和(3)。 可能希望打印Buzz
或Foo
,记住您可以堆叠特征,并且第一个或最后一个(实际上:最后一个)获胜。 那么,为什么Riddle -> Buzz -> Base
不正确? Buzz.msg
是否不调用super.msg
, Buzz
明确指出Base
是它的父级? 这里有些魔术。 当您像我们一样堆叠多个特征时( extends Base with Foo with Bar with Buzz
),Scala编译器会对其进行排序 ( 线性化 ),以便始终存在从每个类到父类的一个路径( Base
)。 该顺序由混入的性状的逆序确定(最后一个获胜并成为第一个)。 你为什么会……? 事实证明,可堆叠特征非常适合围绕真实对象实现多层装饰。 您可以轻松地添加装饰器并移动它们。 我们有一个简单的计算器抽象和一个实现:
trait Calculator {
def increment(x: Int): Int
}
class RealCalculator extends Calculator {
override def increment(x: Int) = {
println(s"increment($x)")
x + 1
}
}
我们提出了三个方面,我们希望在某些情况下有选择地应用这些方面:记录所有increment()
调用,缓存和验证。 首先让我们定义所有这些:
trait Logging extends Calculator {
abstract override def increment(x: Int) = {
println(s"Logging: $x")
super.increment(x)
}
}
trait Caching extends Calculator {
abstract override def increment(x: Int) =
if(x < 10) { //silly caching...
println(s"Cache hit: $x")
x + 1
} else {
println(s"Cache miss: $x")
super.increment(x)
}
}
trait Validating extends Calculator {
abstract override def increment(x: Int) =
if(x >= 0) {
println(s"Validation OK: $x")
super.increment(x)
} else
throw new IllegalArgumentException(x.toString)
}
当然可以创建“原始”计算器:
val calc = new RealCalculator
calc: RealCalculator = RealCalculator@bbd9e6
scala> calc increment 17
increment(17)
res: Int = 18
但是我们可以随意随意混合任意数量的特征混合:
scala> val calc = new RealCalculator with Logging with Caching with Validating
calc: RealCalculator with Logging with Caching with Validating = $anon$1@1aea543
scala> calc increment 17
Validation OK: 17
Cache miss: 17
Logging: 17
increment(17)
res: Int = 18
scala> calc increment 9
Validation OK: 9
Cache hit: 9
res: Int = 10
看看后续的mixins如何启动? 当然,每个混合可以跳过super
调用,例如在缓存命中或验证失败时。 这里要清楚-每个装饰mixin都将Calculator
定义为基本特征。 super.increment()
始终被路由到堆栈中的下一个特征(类声明中的上一个特征)。 这意味着super
更动态,并且依赖于目标用法而不是声明。 我们将在稍后解释这一点,但首先是另一个示例:让我们先将日志记录放在缓存之前,这样,无论缓存命中还是未命中,我们总会得到日志记录。 此外,我们通过简单地跳过它来“禁用”验证:
scala> class VerboseCalculator extends RealCalculator with Caching with Logging
defined class VerboseCalculator
scala> val calc = new VerboseCalculator
calc: VerboseCalculator = VerboseCalculator@f64dcd
scala> calc increment 42
Logging: 42
Cache miss: 42
increment(42)
res: Int = 43
scala> calc increment 4
Logging: 4
Cache hit: 4
res: Int = 5
我答应解释一下如何在下面堆叠。 您应该真的很好奇这个“ funky” super
是如何实现的,因为它不能简单地依赖于与普通super
使用的invokespecial
字节码指令。 不幸的是,它很复杂,但是值得了解和理解,尤其是当堆栈无法按预期工作时。 Calculator
和RealCalculator
完全按照您的预期进行编译:
public interface Calculator {
int increment(int i);
}
public class RealCalculator implements Calculator {
public int increment(int x) {
return x + 1;
}
}
但是下面的类将如何实现?
class FullBlownCalculator
extends RealCalculator
with Logging
with Caching
with Validating
让我们从类本身开始:
public class FullBlownCalculator extends RealCalculator implements Logging, Caching, Validating {
public int increment(int x) {
return Validating$class.increment(this, x);
}
public int Validating$$super$increment(int x) {
return Caching$class.increment(this, x);
}
public int Caching$$super$increment(int x) {
return Logging$class.increment(this, x);
}
public int Logging$$super$increment(int x) {
return super.increment(x);
}
}
你知道这里发生了什么吗? 在我展示所有这些*$class
类的实现之前,花一点时间来面对类声明(尤其是特征顺序)和这些笨拙的*$$super$*
方法。 这是使我们能够连接所有点的缺失部分:
public abstract class Logging$class {
public static int increment(Logging that, int x) {
return that.Logging$$super$increment(x);
}
}
public abstract class Caching$class {
public static int increment(Caching that, int x) {
return that.Caching$$super$increment(x);
}
}
public abstract class Validating$class {
public static int increment(Validating that, int x) {
return that.Validating$$super$increment(x);
}
}
没有帮助吗? 让我们慢慢进行第一步。 在调用FullBlownCalculator
,根据特征堆叠规则, RealBlownCalculator.increment()
应调用Validating.increment()
。 如您所见, Validating.increment()
this
(本身Validating.increment()
转发到静态Validating$class.increment()
隐藏类。 此类预期的实例Validating
,但由于FullBlownCalculator
还扩展了特质,传递this
是好的。
现在查看Validating$class.increment()
。 它几乎不转发FullBlownCalculator.Validating$$super$increment(x)
。 当我们再次回到FullBlownCalculator
我们将注意到此方法委托给静态Caching$class.increment()
。 从这里开始,过程是相似的。 为什么要额外通过static
方法委派? Mixins不知道哪个类将是堆栈中的下一个类(“ next super
”)。 因此,他们只是将委托委托给适当的虚拟$$super$
方法族。 每个使用这些mixin的类都必须实现它们,以提供正确的“超级”。
正确地说:即使是FullBlowCalculator
工作流,编译器也不能直接将Validating$class.increment()
委托给Caching$class.increment()
。 但是,如果我们创建另一个类来反转这些混合表( RealCalculator with Validating with Caching
),则混合表之间的硬编码依赖关系不再有效。 声明顺序是类的责任,而不是混合的责任。 如果您仍然不遵循,这里是FullBlownCalculator.increment()
的完整调用堆栈:
val calc = new FullBlownCalculator
calc increment 42
FullBlownCalculator.increment(42)
`- Validating$class.increment(calc, 42)
`- Validating.Validating$$super$increment(42) (on calc)
`- Caching$class.increment(calc, 42)
`- Caching.Caching$$super$increment(42) (on calc)
`- Logging$class.increment(calc, 42)
`- Logging.Logging$$super$increment(42) (on calc)
`- super.increment(42)
`- RealCalculator.increment(42) (on calc)
现在您知道为什么将其称为“ 线性化 ”了!
scala 线性回归