2012-07-02 4 views
7

Scala non permette di creare vaze laze, solo lazy vals. Ha senso.fare una var lazy in scala

Ma mi sono imbattuto in caso d'uso, dove mi piacerebbe avere capacità simili. Ho bisogno di un titolare variabile pigro. Potrebbe essere assegnato un valore che dovrebbe essere calcolato da un algoritmo dispendioso in termini di tempo. Ma può essere successivamente riassegnato ad un altro valore e mi piacerebbe non chiamare affatto il calcolo del primo valore.

Esempio presumere che vi sia qualche definizione magia var

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

Questo pezzo di codice deve chiamare solo CALC2(), non CALC1

Ho un'idea come posso scrivere questo contenitore con conversioni implicite e e classe contenitore speciale. Sono curioso se c'è qualche caratteristica scala integrata che non richiede a scrivere codice non necessario

risposta

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

Questo funziona:

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

Avere le preoccupazioni implicite me un po 'perché è ampiamente applicabile, che potrebbe portare ad applicazioni impreviste o errori del compilatore su impliciti ambigui.



Un'altra soluzione utilizza un oggetto wrapper con un setter e un metodo getter che implementano il comportamento pigro per voi:

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 per la seconda opzione – paradigmatic

+2

Questa non è una soluzione corretta dal momento che non cattura la natura "caching" di un pigro. Cioè ogni volta che si valuta lv.value la funzione verrà rieseguita (in questo esempio stamperà ancora e ancora). –

1

Si potrebbe semplicemente fare i compilatori funziona se stessi e fare sth in questo modo:

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 

modifica: Questa non è un thread nella sua forma di correnti!

EDIT2:

La differenza alla seconda soluzione di mhs è, che il calcolo accadrà una sola volta, mentre nella soluzione di mhs è chiamato più e più volte.

0

Se si desidera continuare a utilizzare uno lazy val (può essere utilizzato in tipi dipendenti dal percorso ed è thread-safe), è possibile aggiungere un livello di riferimento indiretto nella sua definizione (le soluzioni precedenti utilizzano var s come riferimento indiretto):

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

thunk = ... 
thunk = ... 

Si potrebbe incapsulare questo in un corso se si desidera riutilizzarlo, naturalmente.

0

ho riassunto tutti i consigli forniti per la costruzione di contenitore personalizzato:

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