【发布时间】:2014-01-13 14:44:27
【问题描述】:
Scala 在其标准库中保留了许多非常有用的结构,例如 Option 和 Try。
为什么在缺少上述类型的 C# 等语言选择将其作为库功能实现时,通过拥有自己的关键字来给予 lazy 特殊处理?
【问题讨论】:
标签: scala keyword lazy-evaluation standard-library
Scala 在其标准库中保留了许多非常有用的结构,例如 Option 和 Try。
为什么在缺少上述类型的 C# 等语言选择将其作为库功能实现时,通过拥有自己的关键字来给予 lazy 特殊处理?
【问题讨论】:
标签: scala keyword lazy-evaluation standard-library
确实可以像这样定义一个惰性值:
object Lazy {
def apply[A](init: => A): Lazy[A] = new Lazy[A] {
private var value = null.asInstanceOf[A]
@volatile private var initialized = false
override def toString =
if (initialized) value.toString else "<lazy>@" + hashCode.toHexString
def apply(): A = {
if (!initialized) this.synchronized {
if (!initialized) {
value = init
initialized = true
}
}
value
}
}
implicit def unwrap[A](l: Lazy[A]): A = l()
}
trait Lazy[+A] { def apply(): A }
用法:
val x = Lazy {
println("aqui")
42
}
def test(i: Int) = i * i
test(x)
另一方面,将lazy 作为语言提供的修饰符具有允许它参与统一访问原则的优势。我试图为它查找博客条目,但没有任何超越 getter 和 setter 的内容。这个原则其实更为根本。对于值,统一如下:val、lazy val、def、var、object:
trait Foo[A] {
def bar: A
}
class FooVal[A](val bar: A) extends Foo[A]
class FooLazyVal[A](init: => A) extends Foo[A] {
lazy val bar: A = init
}
class FooVar[A](var bar: A) extends Foo[A]
class FooProxy[A](peer: Foo[A]) extends Foo[A] {
def bar: A = peer.bar
}
trait Bar {
def baz: Int
}
class FooObject extends Foo[Bar] {
object bar extends Bar {
val baz = 42
}
}
Scala 2.6 中引入了惰性值。有一个Lambda the Ultimate comment 表明推理可能与形式化具有循环引用的可能性有关:
循环依赖需要绑定惰性值。惰性值也可用于强制组件初始化按依赖顺序进行。遗憾的是,组件关闭命令必须手动编码
我不知道为什么编译器不能自动处理循环引用;也许有复杂性或性能惩罚的原因。 blog post by Iulian Dragos 证实了其中一些假设。
【讨论】:
当前的惰性实现使用 int 位掩码来跟踪字段是否已初始化,并且没有其他内存开销。该字段在多个惰性值之间共享(每个字段最多 32 个惰性值)。以与库功能相似的内存效率来实现该功能是不可能的。
Lazy 作为一个库大概看起来像这样:
class LazyVal[T](f: =>T) {
@volatile private var initialized = false
/*
this does not need to be volatile since there will always be an access to the
volatile field initialized before this is read.
*/
private var value:T = _
def apply() = {
if(!initialized) {
synchronized {
if(!initialized) {
value = f
initialized = true
}
}
}
value
}
}
这样做的开销将是一个用于生成值的闭包 f 的对象,以及另一个用于 LazyVal 本身的对象。因此,对于像这样经常使用的功能来说,这将是非常重要的。
在 CLR 上,您有值类型,因此如果您将 LazyVal 实现为 C# 中的结构,则开销不会那么糟糕
但是,既然宏可用,将惰性转换为库功能或至少允许自定义惰性初始化可能是个好主意。 lazy val 的很多用例不需要线程同步,所以每次使用lazy val 时都有@volatile/synchronized 开销是很浪费的。
【讨论】:
lazy 的情况下,忽略它并使其变得急切会产生完全不同的结果。