【问题标题】:make a lazy var in scala在scala中做一个懒惰的var
【发布时间】:2012-07-02 07:16:33
【问题描述】:

Scala 不允许创建惰性变量,只能创建惰性变量。有道理。

但我遇到了一些用例,我希望在其中拥有类似的功能。我需要一个惰性变量持有者。它可能被分配一个应该通过耗时的算法计算的值。但它可能稍后会重新分配给另一个值,我根本不想调用第一个值计算。

假设有一些神奇的 var 定义的示例

lazy var value : Int = _
val calc1 : () => Int = ... // some calculation
val calc2 : () => Int = ... // other calculation
value = calc1
value = calc2
val result : Int = value + 1

这段代码应该只调用calc2(),而不是calc1

我知道如何使用隐式转换和特殊容器类编写这个容器。我很好奇是否有任何不需要我编写不必要代码的嵌入式 scala 功能

【问题讨论】:

    标签: scala lazy-evaluation


    【解决方案1】:

    这行得通:

    var value: () => Int = _
    val calc1: () => Int = () => { println("calc1"); 47 }
    val calc2: () => Int = () => { println("calc2"); 11 }
    value = calc1
    value = calc2
    var result = value + 1 /* prints "calc2" */
    
    implicit def invokeUnitToInt(f: () => Int): Int = f()
    

    有隐式让我有点担心,因为它适用范围很广,这可能会导致意外的应用程序或关于模糊隐式的编译器错误。



    另一种解决方案是使用带有 setter 和 getter 方法的包装器对象,为您实现惰性行为:

    lazy val calc3 = { println("calc3"); 3 }
    lazy val calc4 = { println("calc4"); 4 }
    
    class LazyVar[A] {
      private var f: () => A = _
      def value: A = f() /* getter */
      def value_=(f: => A) = this.f = () => f /* setter */
    }
    
    var lv = new LazyVar[Int]
    lv.value = calc3
    lv.value = calc4
    var result = lv.value + 1 /* prints "calc4 */
    

    【讨论】:

    • 这不是一个正确的解决方案,因为它没有捕捉到惰性的“缓存”性质。 IE。每次评估 lv.value 时,都会重新执行该函数(在本例中,它将一次又一次地打印)。
    【解决方案2】:

    你可以简单地自己做编译器工作,然后像这样做:

    class Foo {
      private[this] var _field: String = _
      def field = {
        if(_field == null) {
          _field = "foo" // calc here
        }
        _field
      }
    
      def field_=(str: String) {
        _field = str
      }
    }
    
    scala> val x = new Foo
    x: Foo = Foo@11ba3c1f
    
    scala> x.field
    res2: String = foo
    
    scala> x.field = "bar"
    x.field: String = bar
    
    scala> x.field
    res3: String = bar
    

    编辑:这在当前形式下不是线程安全的!

    编辑2:

    与 mhs 的第二种解决方案的不同之处在于,计算只会发生一次,而在 mhs 的解决方案中,它会被一遍又一遍地调用。

    【讨论】:

      【解决方案3】:

      我总结了所有提供的构建自定义容器的建议:

      object LazyVar {
      
        class NotInitialized extends Exception
      
        case class Update[+T]( update : () => T )
        implicit def impliciţUpdate[T](update: () => T) : Update[T] = Update(update)
      
        final class LazyVar[T] (initial : Option[Update[T]] = None ){
          private[this] var cache : Option[T] = None
          private[this] var data : Option[Update[T]] = initial
      
          def put(update : Update[T]) : LazyVar[T] = this.synchronized {
            data = Some(update)
            this
          }
          def set(value : T) : LazyVar[T] = this.synchronized {
            data = None
            cache = Some(value)
            this
          }
          def get : T = this.synchronized { data match {
            case None => cache.getOrElse(throw new NotInitialized)
            case Some(Update(update)) => {
              val res = update()
              cache = Some(res)
              res
            }
          } }
      
          def := (update : Update[T]) : LazyVar[T] = put(update)
          def := (value : T) : LazyVar[T] = set(value)
          def apply() : T = get
        }
        object LazyVar {
          def apply[T]( initial : Option[Update[T]] = None ) = new LazyVar[T](initial)
          def apply[T]( value : T) = {
            val res = new LazyVar[T]()
            res.set(value)
            res
          }
        }
        implicit def geţLazy[T](lazyvar : LazyVar[T]) : T = lazyvar.get
      
        object Test {
          val getInt1 : () => Int = () => {
            print("GetInt1")
            1
          }
          val getInt2 : () => Int = () => {
            print("GetInt2")
            2
          }
          val li : LazyVar[Int] = LazyVar()
          li := getInt1
          li := getInt2
          val si : Int = li
        }
      }
      

      【讨论】:

        【解决方案4】:
        var value: () => Int = _
        lazy val calc1 = {println("some calculation"); 1}
        lazy val calc2 = {println("other calculation"); 2}
        value = () => calc1
        value = () => calc2
        
        scala> val result : Int = value() + 1
        other calculation
        result: Int = 3
        

        【讨论】:

          【解决方案5】:

          如果你想继续使用lazy val(它可以用于依赖路径的类型并且它是线程安全的),你可以在它的定义中添加一个间接层(以前的解决方案使用vars 作为间接):

          lazy val value: Int = thunk()
          @volatile private var thunk: () => Int = ..
          
          thunk = ...
          thunk = ...
          

          如果你想重用它,当然可以将它封装在一个类中。

          【讨论】:

          • volatile 的好用
          猜你喜欢
          • 1970-01-01
          • 2012-11-04
          • 1970-01-01
          • 1970-01-01
          • 2017-01-21
          • 1970-01-01
          • 2011-11-21
          • 2018-09-17
          • 1970-01-01
          相关资源
          最近更新 更多