scala - 将单例对象编码为惰性值

标签 scala singleton semantics lazy-evaluation

语境

我们正在为 Scala 程序开发静态验证器(早期工作在 this Master's thesis 中描述),目前的重点是验证涉及惰性求值的 Scala 功能。我们主要对感兴趣语义 (行为)特征,而不是其他(尽管重要)方面,例如可理解性或简洁性。

为了简化事情,我们暂时忽略了单例对象可能具有的特殊角色。例如,有些是伴随对象(这可能与其惰性性质正交),或者有些是包对象。

惰性值和单例对象的属性

懒惰的 vals

假设一个惰性值

lazy val v = I

哪里I是初始化块,即确定惰性 val 值的代码。初始化块 I当惰性 val v 时执行第一次取消引用。

单例对象

假设一个单例对象
object Foo {
  C1

  val v1 = I1
  var v2 = I2
  lazy val v3 = I3
  def m() {C2}
}

哪里C1是构成对象Foo的构造函数的代码,其中 I1I3又是初始化块,其中 C2是方法的主体 m .当对象Foo首先使用(取消引用,或分配给变量/字段),然后 C1, I1I2被执行。 I3仅在 Foo.v3 时执行被取消引用(因为 v3 是一个惰性值)和 C2每当 m 时执行叫做。



考虑这个版本的 Foo ,其中单例对象已由惰性 val 和匿名类编码:
// Should probably sit in a package object
lazy val Foo = new {
  C1

  val v1 = I1
  var v2 = I2
  lazy val v3 = I3
  def m() {C2}
}

谁能想到为什么要对单例对象进行编码的原因Foo因为惰性 val 会显示与原始单例对象不同的行为?也就是说,是否存在编码版本与原始代码具有不同语义的(角落)情况?

最佳答案

至少有一个主要的语义差异,我在下面解释了这一点。我认为在多线程上下文中存在一些极端情况,因为对象是在其类的静态初始化程序期间创建的,并且静态初始化程序与锁有一种奇怪的交互。但我不是JVM多线程专家,所以我不知道细节。

无论如何,即使在单线程上下文中也会发生一些事情(并且至少在 Manifest s 的实现中使用,并且可能是 ClassTag s,在当前标准库中)。

考虑以下两个对象:

object A {
  val other = B
}
object B {
  val other = A
}

这是有效的 Scala 代码,可以访问 A.otherB.other将正确返回另一个。这是因为 A 的“val”在调用 super 构造函数后立即初始化。本质上,您可以以某种方式将上述对象转换为这个“低级”代码:
private var instanceA: AClass = null
def A(): AClass = {
  if (instanceA == null)
    new AClass
  instanceA
}
class AClass {
  def this() = {
    // reification of the constructor of A
    super()
    instanceA = this
    this.other = B()
  }
}

当然,B类似翻译。你可以看到,当时 B()被调用来初始化 A.other ,全局变量instanceA已经初始化。因此,当 B已创建并访问 A()初始化 B.other , def A()将立即返回 A 的正确实例.
lazy val s 没有这个早期的初始化,所以编码对象很简单 lazy val s 不会飞。访问任何一个对象都将“死锁”,唯一的线程等待自己。

关于scala - 将单例对象编码为惰性值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24870207/

相关文章:

java - 如果多个用户同时处理它,使用单例模式是否会使我的应用程序变慢

具有单例模式的 PHP 数据库类

html - 没有<ol>或<ul>的<li>的语义意义?

semantics - owl:someValuesFrom 与 owl:minCardinalilty

scala - val 和无参数 def 之间的细微差别

scala - 在 Scala 中返回多个值的函数

Scala 和依赖类型的类型检查

java - 如何在java play框架中的scala模板内编写java代码

java - 单例类中的静态类也是单例吗?

c++ - 什么是 C++11 标准中的消费操作?