Вопрос по lazy-evaluation, scala – сделать ленивый вар в скале

9

Scala не позволяет создавать laze vars, только lazy vals. Это имеет смысл.

Но я столкнулся с вариантом использования, в котором я хотел бы иметь аналогичные возможности. Мне нужен ленивый держатель переменных. Ему может быть присвоено значение, которое должно рассчитываться с помощью трудоемкого алгоритма. Но позже оно может быть переназначено на другое значение, и я бы вообще не хотел вызывать вычисление первого значения.

Пример, если предположить, что есть какое-то магическое определение

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, которая не требует от меня написания ненужного кода

Ваш Ответ

5   ответов
1
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
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 = [email protected]

scala> x.field
res2: String = foo

scala> x.field = "bar"
x.field: String = bar

scala> x.field
res3: String = bar

редактировать: это не потокобезопасный в его форме тока!

edit2:

Отличие от второго решения mhs состоит в том, что вычисление будет происходить только один раз, тогда как в решении mhs оно вызывается снова и снова.

1

lazy val (он может использоваться в зависимых от пути типах и является потокобезопасным), вы можете добавить слой косвенности в его определении (предыдущие решения использовалиvars как косвенное)

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

thunk = ...
thunk = ...

Вы можете инкапсулировать это в классе, если хотите использовать его, конечно.

хорошее использование летучих
1

контейнера:

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

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

Наличие неявного беспокоит меня немного, потому что оно широко применимо, что может привести к неожиданным ошибкам приложений или компилятора из-за неоднозначных последствий.



Другое решение заключается в использовании объекта-оболочки с установщиком и методом получателя, которые реализуют ленивое поведение для вас:

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 */
+1 за второй вариант
Это не правильное решение, так как оно не фиксирует «кэширование». природа ленивая То есть каждый раз, когда вы вычисляете lv.value, функция будет выполняться повторно (в этом примере она будет печататься снова и снова).

Похожие вопросы