Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if a lazy val is initialized without initializing it?

Tags:

scala

Is it possible to determine if a lazy val is initialized, without initializing it?

object TheApp {
    lazy val optionalSubsystem = {
        // ...
        subsystem
    }

    def main(args: Array[String]) {
        bootSubsystemA(this)
        bootSubsystemB(this)

        if (/* optionalSubsystem is initialized */) {
            // more dependencies
        }
    }
}
like image 674
François Beausoleil Avatar asked Jun 12 '13 03:06

François Beausoleil


People also ask

How do you check if a lazy variable is initialized Kotlin?

There is a way, but you have to access the delegate object which is returned by lazy {} : val messageBrokerDelegate = lazy { MessageBroker() } val messageBroker by messageBrokerDelegate if(messageBrokerDelegate. isInitialized()) ... isInitialized is a public method on interface Lazy<T> , here are the docs.

What is lazy initialization in Scala?

Lazy initialization means that whenever an object creation seems expensive, the lazy keyword can be stick before val. This gives it the advantage to get initialized in the first use i.e. the expression inbound is not evaluated immediately but once on the first access. Example: // Scala program of Lazy val.

What is lazy Val?

lazy val is a language feature where the initialization of a val is delayed until it is accessed for the first time.

What does lazy Val mean in Scala?

Scala provides a nice language feature called lazy val that defers the initialization of a variable. The lazy initialization pattern is common in Java programs. Though it seems tempting, the concrete implementation of lazy val has some subtle issues.


4 Answers

This isn't really an answer to your question, and I hate when people do this, but I'm going to do it anyway. I think the best response is: A lazy val isn't appropriate for this, so define a type that does support what you need.

You'll have to refer to the variable as optionalSubsystem() rather than optionalSubsystem, but that's A Good Thing, because with the design you want, obtaining that reference is an observably side-effecting procedure.

class Lazy[A](f: => A, private var option: Option[A] = None) {

  def apply(): A = option match {
    case Some(a) => a
    case None => val a = f; option = Some(a); a
  }

  def toOption: Option[A] = option

}

scala> val optionalSubsystem = new Lazy { "a" }
optionalSubsystem: Lazy[java.lang.String] = Lazy@1210267

scala> optionalSubsystem.toOption.isDefined
res1: Boolean = false

scala> optionalSubsystem()
res2: java.lang.String = a

scala> optionalSubsystem.toOption.isDefined
res12: Boolean = true

Edit - Here's another revision with some modifications thanks to Tomas Mikula:

import scala.language.implicitConversions

object Lazy {

  def lazily[A](f: => A): Lazy[A] = new Lazy(f)

  implicit def evalLazy[A](l: Lazy[A]): A = l()

}

class Lazy[A] private(f: => A) {

  private var option: Option[A] = None

  def apply(): A = option match {
    case Some(a) => a
    case None => val a = f; option = Some(a); a
  }

  def isEvaluated: Boolean = option.isDefined

}

This lets you write lazily { ... } instead of new Lazy { ... }, and optionalSubsystem instead of optionalSubsystem().

scala> import Lazy._
import Lazy._

scala> val optionalSubsystem = lazily { "a" }
optionalSubsystem: Lazy[String] = Lazy@3d0d54

scala> optionalSubsystem.isEvaluated
res0: Boolean = false

scala> optionalSubsystem: String
res1: String = a

scala> optionalSubsystem.isEvaluated
res2: Boolean = true
like image 136
Chris Martin Avatar answered Oct 16 '22 19:10

Chris Martin


You can do something like this:

object TheApp {

    private var _optionalSubsystemInitialized = false

    def optionalSubsystemInitialized = _optionalSubsystemInitialized

    lazy val optionalSubsystem = {
        _optionalSubsystemInitialized = true
        subsystem
    }

}

Whether is it really appropriate to have such side effects in the initialization code of a lazy val is another question.

like image 30
Jean-Philippe Pellet Avatar answered Oct 16 '22 19:10

Jean-Philippe Pellet


But of course you can. A field is just a field.

package lazyside

object Lazy

class Foo {
  lazy val foo = 7
  lazy val bar = { Lazy ; 8 }
}

object Test extends App {
  import scala.reflect.runtime.{ currentMirror => cm }
  import scala.reflect.runtime.universe._

  val x = new Foo

  // method 1: reflect the underlying field
  val im = cm reflect x
  val f  = (typeOf[Foo] declaration TermName("foo")).asTerm.accessed.asTerm
  def foo_? = x synchronized ((im reflectField f).get != 0)

  def yn(b: Boolean) = if (b) "yes" else "no"
  Console println s"Is foo set yet? ${yn(foo_?)}"

  // method 2: check a benign side effect like a class load
  val m = classOf[ClassLoader].getDeclaredMethod("findLoadedClass", classOf[String])
  m setAccessible true
  def bar_? = (m invoke (x.getClass.getClassLoader, "lazyside.Lazy$")) != null
  Console println s"Is bar set yet? ${yn(bar_?)}"

  Console println s"I see that foo is ${x.foo}."
  Console println s"Is foo set yet? ${yn(foo_?)}"
  Console println s"I see that bar is ${x.bar}."
  Console println s"Is bar set yet? ${yn(bar_?)}"
  Console println s"I see that x is loaded by a ${x.getClass.getClassLoader.getClass}"
}

The caveat is that thread-safety of foo_? relies on the lazy computation acquiring the monitor of the instance x. There is talk of changing that.

Also, obviously, testing the field value only works if the init value is not the default value (null.asInstanceOf[T]).

The second method relies on the class Lazy$ getting loaded by the lazy init. It would be a bit safer to squirrel the object inside Foo. In any case, that particular side-effect is one-shot. That might satisfy the use case of subsystem start-up.

With the unsurprising output:

Is foo set yet? no
Is bar set yet? no
I see that foo is 7.
Is foo set yet? yes
I see that bar is 8.
Is bar set yet? yes
I see that x is loaded by a class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader

Compiled in 2.11. For 2.10, use newTermName instead of TermName.

like image 2
som-snytt Avatar answered Oct 16 '22 19:10

som-snytt


How about this workaround?

val used = new AtomicBoolean(false)

lazy val o: String = {
  used.set(true)
  "aaa"
}

if (used.get()) { /* initialized */ }
like image 2
botkop Avatar answered Oct 16 '22 20:10

botkop