Подтвердить что ты не робот

Как проверить, инициализирован ли ленивый val, не инициализируя его?

Можно ли определить, инициализирован ли ленивый val, не инициализируя его?

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

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

        if (/* optionalSubsystem is initialized */) {
            // more dependencies
        }
    }
}
4b9b3361

Ответ 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)
  }
}