2012-07-02 18 views
7

Scala nie pozwala tworzyć laze vars, tylko leniwych vals. To ma sens.Zrobić leniwy var w scala

Ale ja wpadł na przypadek użycia, gdzie chciałbym mieć podobną funkcję. Potrzebuję leniwego właściciela zmiennej. Można mu przypisać wartość, którą należy obliczyć za pomocą czasochłonnego algorytmu. Ale może być później ponownie przypisana do innej wartości i nie chciałbym w ogóle wywoływać obliczeń pierwszej wartości.

przykład zakładając, że jest jakaś definicja magii var

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

Ten kawałek kodu powinien wywołać tylko CALC2(), nie CALC1

mam pomysł jak mogę napisać ten pojemnik z ukrytych konwersji i i specjalna klasa kontenerów. Jestem osobliwości jeśli jest jakaś wbudowana funkcja scala, które nie wymagają mi napisać niepotrzebnego kodu

Odpowiedz

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 
6

to działa:

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

Mając ukryte zmartwień mnie nieco, ponieważ jest powszechnie stosowane, co może prowadzić do nieoczekiwanych aplikacji lub błędów kompilatora związanych z niejednoznacznymi implikacjami.



Innym rozwiązaniem jest użycie obiektu otoki z seter i metody getter, które wdrażają leniwy zachowania dla Ciebie:

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

+1 dla drugiej opcji – paradigmatic

+2

To nie jest poprawne rozwiązanie, gdyż nie uchwycić „buforowania” natury leniwy. To znaczy. za każdym razem, gdy ocenisz wartość lv.fue, funkcja zostanie ponownie wykonana (w tym przykładzie będzie drukowana wielokrotnie). –

1

Można po prostu zrobić kompilatory prac siebie i zrobić czegoś takiego:

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 

edytuj: To nie jest bezpieczne dla wątków w swojej formie prądów!

Edit2:

Różnica w stosunku do drugiego roztworu MHS jest, że obliczenie nastąpi tylko raz, podczas gdy w roztworze MHS jest nazywany w kółko.

0

Jeśli chcesz nadal korzystać z lazy val (można go używać w typach zależnych od ścieżki i jest bezpieczny dla wątków), możesz dodać warstwę pośrednią w jej definicji (poprzednie rozwiązania używają var s jako pośrednika):

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

thunk = ... 
thunk = ... 

Można to zawrzeć w klasie, jeśli chcesz go ponownie użyć, oczywiście.

0

mam podsumować wszystkie przewidziane rad na budowę własnego pojemnika:

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