Scala在其标准库中保留了许多非常有用的构造,如Option和Try.
当缺少上述类型的C#等语言选择将其作为库功能实现时,为什么懒惰通过拥有自己的关键字给予特殊处理?
确实可以定义一个惰性值,例如:
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终极评论表明推理可能与形式化循环引用的可能性有关:
循环依赖关系需要与惰性值绑定.延迟值还可用于强制按依赖顺序进行组件初始化.遗憾的是,组件关闭顺序必须手动编码
我不知道为什么编译器无法自动处理循环引用; 也许有复杂性或表现性的原因.一个博客帖子是Iulian Dragos的证实一些假设.
当前的延迟实现使用int位掩码来跟踪字段是否已初始化,并且没有其他内存开销.该字段在多个惰性val之间共享(每个字段最多32个惰性值).实现具有与库特征类似的存储器效率的特征是不可能的.
作为库的懒惰可能看起来大致如下:
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实现为struct,那么开销就不那么糟了
但是,现在宏可用,将惰性转换为库特征或至少允许自定义延迟初始化可能是个好主意.许多lazy val的使用情况不需要线程同步,因此每次使用lazy val时都要浪费@ volatile/synchronized开销.