2013-06-12 23 views
13

lazy valが初期化されていないかどうかを確認することはできますか?lazy 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

"Akkaの俳優を起動します。俳優が起動した場合は、監督したいと思います"。このユースケースは、あなたが本当に知りたいことであれば、質問に記載する必要があります。 –

答えて

12

これは本当にあなたの質問に対する答えではなく、人々がこれを行うときは嫌いですが、とにかくそれをやろうとしています。私は最良のレスポンスは次のようなものだと考えています:怠惰なvalはこれには適切ではないので、必要なものをサポートする型を定義してください。

あなたはoptionalSubsystem()ではなくoptionalSubsystemとして変数を参照する必要がありますが、それはその参照を取得することobservably副作用の手順である、なぜならあなたが望むデザインで、Aグッドシングです。

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 

} 

これはあなたの代わりにoptionalSubsystem()lazily { ... }代わりのnew Lazy { ... }、および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

優れたデザインは* good *です。正しい方向に私を指してくれてありがとう。 –

+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のモニターを取得する遅延計算に依存しているということです。それを変える話があります。

また、明らかにフィールド値のテストは、初期値がデフォルト値(null.asInstanceOf[T])でない場合にのみ機能します。

第2の方法は、遅延初期化によってロードされるクラスLazy$に依存します。 Foo内のオブジェクトをリスリングするのは少し安全です。いずれにせよ、その特定の副作用はワンショットです。これは、サブシステムの起動のユースケースを満たすかもしれません。 2.11でコンパイル

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.10の場合はTermNameの代わりにnewTermNameを使用してください。

+1

IMOの最大の問題は、初期化されたフィールドの真の値である可能性があるデフォルト値をテストしていることです。また、副作用チェックの例が複雑すぎます。それでは、怠惰なイニシャライザにフラグを設定するだけでいいのですか? –

+0

私は、実際にユースケースに当てはまらないデフォルト値を区別する問題に気付きました。有用な副作用についての私の考えは、サブシステムをロードしているなら、新しいクラスを読み込んでいる可能性があるので、それは便利なテストです。 –

0

ない直接、しかし、なぜあなたはちょうどこのように周り、あなたのロジックをシフトしていない:

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

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

この方法では、「より多くの依存関係が」最適な時にロードされます(これらは必要ありません決して場合を含みます)

+0

この特定の例では、私はAkka俳優を起動します。アクターがブートされた場合、私はそれを監視したいが、そうでなければスーパーバイザのリストに追加したくないので、起動したかどうかを確認する。 –

関連する問題