问题
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 var
s 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