8

我一直试图了解隐式参数在 Scala 中是如何工作的。据我所知,隐式参数解析是这样的:

  1. 将对象显式传递给方法。
  2. 范围内定义的隐式定义。
  3. 用作隐式参数的类的伴随对象

然而,当我开始与懒惰的 vals 一起玩这个时,我有点吃惊。似乎懒惰的 val 只使用最后的解析规则。下面是一些示例代码来说明:

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
    }
}

调用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' ...

为什么编译器在案例 2 中调用 mixInList 时没有发现混入了隐式 Bar。在案例 3 中,它在访问列表时也错过了本地定义的隐式 Bar。

有什么方法可以将隐式参数与不使用伴随对象中定义的隐式定义的惰性 val 一起使用?

4

2 回答 2

4

那是因为没有其他implicit Bar的,当编译器编译 Foo 类时。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$;
  }
}

lazy val只是一个检查变量是否已经设置并返回它的方法,或者设置它然后返回它。所以你的mixin根本没有被考虑在内。如果需要,您必须自己处理初始化。

于 2012-04-15T14:07:44.400 回答
2

尽管 scala 不支持带有隐式参数的惰性 val,但您可以使用选项自己定义。因此,一个解决方案是替换:

lazy val list = initialize

经过

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

然后运行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' ...

请注意,如果您有mutable options,您可以只用两行替换您的 lazy val 以获得相同的结果。

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

就个人而言,我希望有一天 Scala 能让我们用一行代码来写这个:

lazy val list(implicit f: Bar) = initialize

这将是非常有意义的:任何时候你想访问变量列表的值,你都需要一个范围内的 Bar ,尽管只有第一次计算很重要。

于 2017-03-22T15:12:28.260 回答