Question

Scala keeps a lot of very useful constructs like Option and Try in its standard library.

Why is lazy given special treatment by having its own keyword when languages such as C#, which lacks afore mentioned types, choose to implement it as a library feature?

Was it helpful?

Solution

It is true that you could define a lazy value for example like this:

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 }

Usage:

val x = Lazy {
  println("aqui")
  42
}

def test(i: Int) = i * i

test(x)

On the other hand, having lazy as a language provided modifier has the advantage of allowing it to participate in the uniform access principle. I tried to look up a blog entry for it, but there isn't any that goes beyond getters and setters. This principle is actually more fundamental. For values, the following are unified: 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
  }
}

Lazy values were introduced in Scala 2.6. There is a Lambda the Ultimate comment which suggests that the reasoning might have to do with formalising the possibility to have cyclic references:

Cyclic dependencies require binding with lazy values. Lazy values can also be used to enforce that component initialization occurs in dependency order. Component shutdown order, sadly, must be coded by hand

I do not know why cyclic references could not be automatically handled by the compiler; perhaps there were reasons of complexity or performance penality. A blog post by Iulian Dragos confirms some of these assumptions.

OTHER TIPS

The current lazy implementation uses an int bitmask to track if a field has been initialized, and no other memory overhead. This field is shared between multiple lazy vals (up to 32 lazy vals per field). It would be impossible to implement the feature with a similar memory efficiency as a library feature.

Lazy as a library would probably look roughly like this:

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

The overhead of this would be an object for the closure f that generates the value, and another object for the LazyVal itself. So it would be substantial for a feature that is used as often as this.

On the CLR you have value types, so the overhead is not as bad if you implement your LazyVal as a struct in C#

However, now that macros are available, it might be a good idea to turn lazy into a library feature or at least allow to customize the lazy initialiation. Many use cases of lazy val do not require thread synchronization, so it is wasteful to have the @volatile/synchronized overhead every time you use lazy val.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top