2013-06-12 39 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。如果演員啓動了,我想監督它,但是如果不是,那麼我不想將它添加到主管的列表中,因此檢查它是否被啓動。 –

相關問題