简体   繁体   English

Scala中的惰性val和隐式参数

[英]Lazy vals and implicit parameters in Scala

I have been trying to get a grasp on how implicit parameters work in Scala. 我一直试图了解隐式参数在Scala中的工作原理。 As far as I can tell the implicit parameter resolution goes something like this: 据我所知,隐式参数解析如下:

  1. Explicitly passing an object to the method. 将对象显式传递给方法。
  2. implicit definitons defined in scope. 范围中定义的隐式定义。
  3. Companion object of the class used as a implicit parameter 用作隐式参数的类的伴随对象

However, when I started playing around with this in conjunction lazy vals I got a bit of a supprise. 然而,当我开始玩这个懒惰的vals时,我得到了一些帮助。 It seems that lazy vals only ever use the last resolution rules. 懒惰的val似乎只使用最后的解析规则。 Here is some sample code to illustrate: 以下是一些示例代码:

class Bar(val name:String)
object Bar { implicit def bar = new Bar("some default bar") }

class Foo {
  lazy val list = initialize
  def initialize(implicit f:Bar) = {
    println("initialize called with Bar: '" + f.name + "' ...")
    List[Int]()
  }
}

trait NonDefaultBar extends Foo {
  implicit def f = new Bar("mixed in implicit bar")
  def mixedInInit = initialize
  lazy val mixedInList = list
}

object Test {
    def test = {
      println("Case 1: with implicitp parameter from companion object")
      val foo1 = new Foo
      foo1.list
      foo1.initialize

      println("Case 2: with mixedin implicit parameter overriding the default one...")
      val foo2 = new Foo with NonDefaultBar 
      foo2.mixedInList

      val foo3 = new Foo with NonDefaultBar 
      foo3.mixedInInit

      println("Case 3: with local implicit parameter overriding the default one...")
      implicit def nonDefaultBar = new Bar("locally scoped implicit bar")
      val foo4 = new Foo 
      foo4.list
      foo4.initialize
    }
}

Calling Test.test gives the following output: 调用Test.test给出以下输出:

Case 1: with implicitp parameter from companion object 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'some default bar' ... 
Case 2: with mixedin implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'mixed in implicit bar'... 
Case 3: with local implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'locally scoped implicit bar' ...

Why does the compiler not catch that there is a implict Bar mixed in when calling mixedInList in Case 2. In Case 3 it also misses the locally defined implicit Bar when accessing the list. 为什么编译器在第2种情况下调用mixedInList时没有发现有混合的条件。在案例3中,它在访问列表时也错过了本地定义的隐式Bar。

Are there any ways to use implicit parameters with lazy vals that does not use the implicit defined in the companion object? 是否有任何方法可以使用隐式val的隐式参数,而不使用随播对象中定义的隐式?

That is because there is no other implicit Bar , when the compiler compiles the Foo class. 这是因为当编译器编译Foo类时,没有其他implicit Bar The decompiled code in Java look like this: Java中反编译的代码如下所示:

public class Foo
  implements ScalaObject
{
  private List<Object> list;
  public volatile int bitmap$0;

  public List<Object> list()
  {
    if (
      (this.bitmap$0 & 0x1) == 0);
    synchronized (this)
    {
      if (
        (this.bitmap$0 & 0x1) == 0) {
        this.list = initialize(Bar..MODULE$.bar()); this.bitmap$0 |= 1; } return this.list;
    }
  }
  public List<Object> initialize(Bar f) { Predef..MODULE$.println(new StringBuilder().append("initialize called with Bar: '").append(f.name()).append("' ...").toString());
    return Nil..MODULE$;
  }
}

The lazy val is just a method that checks if the variable is already set and either returns it, or sets it and then returns it. lazy val只是一种方法,它检查变量是否已经设置并返回它,或者设置它然后返回它。 So your mixin is not taken into account at all. 所以你的mixin根本没有被考虑在内。 If you want that, you have to take care of the initialization yourself. 如果你想要,你必须自己处理初始化。

Although scala does not support lazy vals with implicit parameters, you can define that yourself using options. 虽然scala不支持带隐式参数的延迟val,但您可以使用选项自行定义。 Therefore, a solution is to replace: 因此,解决方案是替换:

lazy val list = initialize

by 通过

private var _list: Option[List[Int]] = None
def list(implicit f: Bar) = 
  _list.getOrElse{
    _list = Some(initialize)
    _list.get
  }

Then running Test.test displays the expected result: 然后运行Test.test显示预期的结果:

Case 1: with implicitp parameter from companion object
initialize called with Bar: 'some default bar' ...
initialize called with Bar: 'some default bar' ...
Case 2: with mixedin implicit parameter overriding the default one...
initialize called with Bar: 'mixed in implicit bar' ...
initialize called with Bar: 'mixed in implicit bar' ...
Case 3: with local implicit parameter overriding the default one...
initialize called with Bar: 'locally scoped implicit bar' ...
initialize called with Bar: 'locally scoped implicit bar' ...

Note that if you had mutable options , you could replace your lazy val with only two lines to get the same result. 请注意,如果您有可变选项 ,则可以仅使用两行替换延迟val以获得相同的结果。

private val _list: MutableOpt[List[Int]] = MutableOpt.from(None)
def list(implicit f: Bar) = _list.getOrSet(initialize)

Personally, I hope one day Scala will let us write this using one line: 就个人而言,我希望有一天Scala会让我们用一行来写这个:

lazy val list(implicit f: Bar) = initialize

which would be perfectly meaningful: At any time you want to access the value of the variable list, you need a Bar in scope, although only the first computation will matter. 这将是非常有意义的:在任何时候你想要访问变量列表的值,你需要一个范围内的条,虽然只有第一个计算是重要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM