Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is "lazy" a keyword rather than a standard-library type?

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?

like image 521
megri Avatar asked Dec 25 '13 20:12

megri


People also ask

Why is lazy evaluation better?

The benefits of lazy evaluation include: The ability to define control flow (structures) as abstractions instead of primitives. The ability to define potentially infinite data structures. This allows for more straightforward implementation of some algorithms.

What is the use of lazy?

Remarks. Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program. To prepare for lazy initialization, you create an instance of Lazy<T>.

What does lazy evaluation mean what is the main advantage of lazy evaluation?

Lazy evaluation is an evaluation strategy which holds the evaluation of an expression until its value is needed. It avoids repeated evaluation. Haskell is a good example of such a functional programming language whose fundamentals are based on Lazy Evaluation.

What does lazy mean in programming?

In a nutshell, "lazy" means to defer an action until it becomes necessary, if ever. If the result of the action is never used, the action will never be carried out, saving some work.


2 Answers

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.

like image 136
0__ Avatar answered Sep 28 '22 17:09

0__


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.

like image 34
Rüdiger Klaehn Avatar answered Sep 28 '22 16:09

Rüdiger Klaehn