2013-06-12 38 views
13

是否可以确定一个惰性val是否已初始化,而无需初始化它?如何检查一个懒惰的val是否被初始化而没有初始化呢?

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

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

     if (/* optionalSubsystem is initialized */) { 
      // more dependencies 
     } 
    } 
} 
+0

不,但是什么决定了optionalSubsystem是否会被提出?它是运行时信息吗?或编译时信息? – stew

+0

所有运行时。我找到了一种方法:'optionalSubsystem'有一个副作用 - 它引导一个演员。我检查了副作用,直到这个答案更好。 –

+0

“我启动了一位阿卡演员,如果演员启动了,我想监督它”。这个用例应该在问题中说明,如果这是你真正想知道的。 –

回答

12

这不是真的回答你的问题,而且我讨厌人们这样做,但我仍然会这样做。我认为最好的回应是:懒惰的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 

编辑 - 这是另一个版本的一些修改感谢托马斯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 

} 

这可以让你写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 
+0

更好的设计是*好*。感谢您指点我正确的方向。 –

+0

这不是线程安全的。人们可以说,“很容易做到这一点”,或者“有更好的方法来协调多个线程/演员。” –

+0

有人说它应该是线程安全的吗? –

3

你可以做这样的事情:

object TheApp { 

    private var _optionalSubsystemInitialized = false 

    def optionalSubsystemInitialized = _optionalSubsystemInitialized 

    lazy val optionalSubsystem = { 
     _optionalSubsystemInitialized = true 
     subsystem 
    } 

} 

不管是不是真的适合在一个lazy val的初始化代码等副作用则是另一个问题。

+0

我想说这是严格的不适当的。我赞同http://stackoverflow.com/questions/12959777/why-would-i-want-to-re-implement-lazy引用的一句话:“当语义需要懒惰时避免使用懒惰。在这些情况下,它是最好是明确的,因为它使成本模型明确,副作用可以更精确地控制。“ –

+0

@ChrisMartin - 如果你同意那个引用,这是否意味着你可以解释它的含义?你连接的问题是我的问题,没有人发布过令人满意的答案。 – DaoWen

+0

@DaoWen - 我一直在想你的问题,我不能拿出任何有说服力的答案。 –

1

但是当然可以。一个领域只是一个领域。

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])时,测试字段值才起作用。

第二种方法依赖于由惰性init加载的类Lazy$。松鼠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

+1

IMO最大的问题是你正在测试一个默认值,这很可能是初始化字段的真实值,而你的副作用检查的例子太复杂了。那么,为什么不在懒惰初始化器中设置一个标志呢? –

+0

我注意到区分默认值的问题,它实际上并不适用于用例;我对有用的副作用的想法是,如果你正在加载一个子系统,可能你正在加载一个新类,所以这是一个方便的测试。 –

0

不是直接的,但你为什么不就在转移你的逻辑是这样的:

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

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

这种方式“更加依赖”获得的最佳时间加载(包括从来没有如果不需要的话)

+0

在这个特定的例子中,我启动了一个Akka actor。如果演员启动了,我想监督它,但是如果不是,那么我不想将它添加到主管的列表中,因此检查它是否被启动。 –

相关问题