为什么“懒惰”是关键字而不是标准库类型?

时间:2013-12-25 20:19:20

标签: scala keyword lazy-evaluation standard-library

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

为什么 lazy 通过拥有自己的关键字给予特殊处理,例如C#等语言(缺少上述类型)选择将其作为库功能实现?

2 个答案:

答案 0 :(得分:15)

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

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

Scala 2.6中引入了懒惰值。有一个Lambda the Ultimate comment表明推理可能与形式化循环引用的可能性有关:

  

循环依赖关系需要使用延迟值进行绑定。延迟值还可用于强制按依赖顺序进行组件初始化。遗憾的是,组件关闭顺序必须手动编码

我不知道为什么循环引用不能由编译器自动处理;也许有复杂性或表现性的原因。 blog post by Iulian Dragos证实了其中一些假设。

答案 1 :(得分:10)

当前的延迟实现使用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实现为结构,开销就不会那么糟糕

但是,现在宏已经可用,将懒惰转换为库功能或者至少允许自定义延迟初始化可能是个好主意。许多lazy val的用例不需要线程同步,因此每次使用lazy val时都要浪费@ volatile / synchronized开销。