为什么"lazy"是关键字而不是标准库类型?



Scala在其标准库中保留了许多非常有用的构造,如Option和Try。

当缺乏上述类型的C#等语言选择将其作为库功能来实现时,为什么lazy会因为拥有自己的关键字而受到特殊处理?

确实可以定义一个惰性值,例如:

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之外,没有其他内容。这个原则实际上更为根本。对于值,以下是统一的:vallazy valdefvarobject:

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

Lazy值是在Scala 2.6中引入的。有一个Lambda the Ultimate评论表明,推理可能与形式化循环引用的可能性有关:

循环依赖项需要绑定惰性值。惰性值也可以用于强制组件初始化按依赖关系顺序进行。遗憾的是,部件关闭命令必须通过手动进行编码

我不知道为什么编译器不能自动处理循环引用;也许是由于复杂性或表现不佳。Iulian Dragos的一篇博客文章证实了其中的一些假设。

当前的惰性实现使用int位掩码来跟踪字段是否已初始化,并且没有其他内存开销。该字段在多个延迟val之间共享(每个字段最多32个延迟val)。不可能实现具有与库功能类似的内存效率的功能。

懒惰的图书馆可能大致是这样的:

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上,您有值类型,因此如果您在C#中将LazyVal实现为结构,则开销不会那么糟糕

然而,既然宏已经可用,那么将懒惰变成库功能可能是个好主意,或者至少允许自定义懒惰初始化。懒惰val的许多用例不需要线程同步,因此每次使用懒惰val时都会有@volatile/synchronized开销,这是浪费。

最新更新