Как проверить, инициализирован ли ленивый val, не инициализируя его?
Можно ли определить, инициализирован ли ленивый val, не инициализируя его?
object TheApp {
lazy val optionalSubsystem = {
// ...
subsystem
}
def main(args: Array[String]) {
bootSubsystemA(this)
bootSubsystemB(this)
if (/* optionalSubsystem is initialized */) {
// more dependencies
}
}
}
Ответы
Ответ 1
Это не ответ на ваш вопрос, и я ненавижу, когда люди это делают, но я все равно сделаю это. Я думаю, что лучший ответ: ленивый val не подходит для этого, поэтому определите тип, который поддерживает то, что вам нужно.
Вам нужно будет ссылаться на переменную как optionalSubsystem()
, а не на optionalSubsystem
, но на "Хорошую вещь", потому что с помощью дизайна, который вы хотите, получение этой ссылки является наблюдаемой процедурой бокового эффекта.
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] = [email protected]
scala> optionalSubsystem.toOption.isDefined
res1: Boolean = false
scala> optionalSubsystem()
res2: java.lang.String = a
scala> optionalSubsystem.toOption.isDefined
res12: Boolean = true
Изменить. Здесь другая версия с некоторыми изменениями благодаря Томасу Микуле:
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
}
Это позволяет вам писать lazily { ... }
вместо new Lazy { ... }
и optionalSubsystem
вместо optionalSubsystem()
.
scala> import Lazy._
import Lazy._
scala> val optionalSubsystem = lazily { "a" }
optionalSubsystem: Lazy[String] = [email protected]
scala> optionalSubsystem.isEvaluated
res0: Boolean = false
scala> optionalSubsystem: String
res1: String = a
scala> optionalSubsystem.isEvaluated
res2: Boolean = true
Ответ 2
Вы можете сделать что-то вроде этого:
object TheApp {
private var _optionalSubsystemInitialized = false
def optionalSubsystemInitialized = _optionalSubsystemInitialized
lazy val optionalSubsystem = {
_optionalSubsystemInitialized = true
subsystem
}
}
Действительно ли целесообразно иметь такие побочные эффекты в коде инициализации a lazy val
, это другой вопрос.
Ответ 3
Но, конечно, вы можете. Поле - это просто поле.
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}"
}
Предостережение заключается в том, что безопасность потока foo_?
основана на ленивом вычислении, которое получает монитор экземпляра x
. Есть разговоры об изменении этого.
Кроме того, очевидно, что тестирование значения поля работает только в том случае, если значение init не является значением по умолчанию (null.asInstanceOf[T]
).
Второй метод использует класс Lazy$
, загружаемый ленивым init. Было бы намного безопаснее бегать по объекту внутри Foo. В любом случае, этот побочный эффект является одним выстрелом. Это может удовлетворить пример использования подсистемы.
С неудивительным выходом:
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
Составлено в 2.11. Для 2.10 используйте newTermName
вместо TermName
.
Ответ 4
Как насчет этого обходного пути?
val used = new AtomicBoolean(false)
lazy val o: String = {
used.set(true)
"aaa"
}
if (used.get()) { /* initialized */ }
Ответ 5
Не прямо, но почему бы вам просто не сдвинуть свою логику так:
object TheApp {
lazy val optionalSubsystem = {
// ...
subsystem
// more dependencies
}
def main(args: Array[String]) {
bootSubsystemA(this)
bootSubsystemB(this)
}
}
Таким образом, "больше зависимостей" загружаются в оптимальное время (в том числе никогда, если они не нужны)
Ответ 6
Чтобы оставаться неизменным, вы должны справиться с изменением состояния.
Вы можете использовать следующий Ленивый, чтобы делать то, что вы хотите:
trait Lazy[T] {
def act[U](f: T => U): (Lazy[T], U)
def actIfInitialized[U](f: T => U): (Lazy[T], Option[U])
def isInitialized: Boolean
}
case class UnInitializedLazy[T](builder: () => T) extends Lazy[T] {
override def isInitialized: Boolean = false
override def act[U](f: T => U): (Lazy[T], U) = {
InitializedLazy(builder()).act(f)
}
override def actIfInitialized[U](f: T => U): (Lazy[T], Option[U]) = {
(this, None)
}
}
case class InitializedLazy[T](thing: T) extends Lazy[T] {
override def isInitialized: Boolean = false
override def act[U](f: T => U): (Lazy[T], U) = {
(this, f(thing))
}
override def actIfInitialized[U](f: T => U): (Lazy[T], Option[U]) = {
(this, Some(f(thing)))
}
}
вы бы использовали это так:
class example extends FlatSpec with Matchers {
it should "initialize and act" in {
val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")
val (bob, upperBob) = personToUpper(lazyBob)
upperBob shouldBe "BOB"
bob.isInitialized shouldBe true
}
it should "act since it was initialized" in {
val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")
val (bob, upperBob) = personToUpper(lazyBob)
var res: Boolean = false
upperBob shouldBe "BOB"
bob.isInitialized shouldBe true
bob.actIfInitialized(_ => res = true)
res shouldBe true
}
it should "not act since it was not initialized" in {
val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")
var res: Boolean = false
lazyBob.isInitialized shouldBe false
lazyBob.actIfInitialized(_ => res = true)
lazyBob.isInitialized shouldBe false
res shouldBe false
}
def personToUpper(person: Lazy[String]): (Lazy[String], String) = {
// Here you will initialize it and do the computing you want.
// The interest is that you will not need to know how to instanciate Bob
// since it was defined before, you just do your computations and return a state.
person.act(p => p.toUpperCase)
}
}