make a lazy var in scala

為{幸葍}努か 提交于 2019-12-19 05:34:17

问题


Scala does not permit to create laze vars, only lazy vals. It make sense.

But I've bumped on use case, where I'd like to have similar capability. I need a lazy variable holder. It may be assigned a value that should be calculated by time-consuming algorithm. But it may be later reassigned to another value and I'd like not to call first value calculation at all.

Example assuming there is some magic var definition

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

This piece of code should only call calc2(), not calc1

I have an idea how I can write this container with implicit conversions and and special container class. I'm curios if is there any embedded scala feature that doesn't require me write unnecessary code


回答1:


This works:

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()

Having the implicit worries me slightly because it is widely applicable, which might lead to unexpected applications or compiler errors about ambiguous implicits.



Another solution is using a wrapper object with a setter and a getter method that implement the lazy behaviour for you:

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 */



回答2:


You could simply do the compilers works yourself and do sth like this:

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

edit: This is not thread safe in its currents form!

edit2:

The difference to the second solution of mhs is, that the calculation will only happen once, whilst in mhs's solution it is called over and over again.




回答3:


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



回答4:


If you want to keep on using a lazy val (it can be used in path-dependent types and it's thread safe), you can add a layer of indirection in its definition (previous solutions use vars as an indirection):

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

thunk = ...
thunk = ...

You could encapsulate this in a class if you wanted to reuse it, of course.




回答5:


I've summarized all provided advices for building custom container:

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
  }
}


来源:https://stackoverflow.com/questions/11289243/make-a-lazy-var-in-scala

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!