scala 线性回归_Scala特征的实现和互操作性。 第二部分:特征线性化

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的输出是什么?

  1. Riddle -> Base
  2. Riddle -> Buzz -> Base
  3. Riddle -> Foo -> Base
  4. Riddle -> Buzz -> Bar -> Foo -> Base

不是(1),因为Base.msg被我们扩展的所有特征所覆盖,所以这并不奇怪。 但这不是(2)和(3)。 可能希望打印BuzzFoo ,记住您可以堆叠特征,并且第一个或最后一个(实际上:最后一个)获胜。 那么,为什么Riddle -> Buzz -> Base不正确? Buzz.msg是否不调用super.msgBuzz明确指出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字节码指令。 不幸的是,它很复杂,但是值得了解和理解,尤其是当堆栈无法按预期工作时。 CalculatorRealCalculator完全按照您的预期进行编译:

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)

现在您知道为什么将其称为“ 线性化 ”了!


翻译自: https://www.javacodegeeks.com/2013/04/scala-traits-implementation-and-interoperability-part-ii-traits-linearization.html

scala 线性回归

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值