Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make a lazy var in scala

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

like image 786
ayvango Avatar asked Jul 02 '12 07:07

ayvango


2 Answers

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
  }
}
like image 54
ayvango Avatar answered Sep 22 '22 21:09

ayvango


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.

like image 27
Iulian Dragos Avatar answered Sep 20 '22 21:09

Iulian Dragos