make a lazy var in scala

后端 未结 5 1884
我在风中等你
我在风中等你 2021-01-11 15:04

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 var

相关标签:
5条回答
  • 2021-01-11 15:41

    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
      }
    }
    
    0 讨论(0)
  • 2021-01-11 15:47
    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
    
    0 讨论(0)
  • 2021-01-11 15:48

    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 */
    
    0 讨论(0)
  • 2021-01-11 15:50

    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.

    0 讨论(0)
  • 2021-01-11 15:58

    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.

    0 讨论(0)
提交回复
热议问题