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?
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.
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>.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With