2012-11-04 11 views
10

in una risposta ad una domanda StackOverflow ho creato un flusso come val, in questo modo:Devo usare val o def quando definisco un flusso?

val s:Stream[Int] = 1 #:: s.map(_*2) 

e qualcuno mi ha detto che def dovrebbe essere usato al posto di val perché Scala Kata si lamenta (come fa il foglio di lavoro Scala in Eclipse) che un "riferimento in avanti si estende sulla definizione di valore s".

Ma gli esempi nei documenti Stream utilizzano val. Quale è giusto?

risposta

21

Scalac e REPL stanno bene con quel codice (usando val) finché la variabile è un campo di una classe piuttosto che una variabile locale. Puoi rendere la variabile pigra per soddisfare Scala Kata, ma in genere non vorresti usare def in questo modo (ovvero, def un Stream in termini di se stesso) in un vero programma. In tal caso, viene creato un nuovo flusso ogni volta che viene richiamato il metodo, quindi i risultati dei calcoli precedenti (che vengono salvati nello Stream) non possono mai essere riutilizzati. Se si utilizzano molti valori da un tale Stream, le prestazioni saranno terribili e alla fine si esaurirà la memoria.

Questo programma illustra il problema con l'utilizzo DEF in questo modo:

// Show the difference between the use of val and def with Streams. 

object StreamTest extends App { 

    def sum(p:(Int,Int)) = { println("sum " + p); p._1 + p._2 } 

    val fibs1: Stream[Int] = 0 #:: 1 #:: (fibs1 zip fibs1.tail map sum) 
    def fibs2: Stream[Int] = 0 #:: 1 #:: (fibs2 zip fibs2.tail map sum) 

    println("========== VAL ============") 
    println("----- Take 4:"); fibs1 take 4 foreach println 
    println("----- Take 5:"); fibs1 take 5 foreach println 

    println("========== DEF ============") 
    println("----- Take 4:"); fibs2 take 4 foreach println 
    println("----- Take 5:"); fibs2 take 5 foreach println 
} 

Ecco l'output:

========== VAL ============ 
----- Take 4: 
0 
1 
sum (0,1) 
1 
sum (1,1) 
2 
----- Take 5: 
0 
1 
1 
2 
sum (1,2) 
3 
========== DEF ============ 
----- Take 4: 
0 
1 
sum (0,1) 
1 
sum (0,1) 
sum (1,1) 
2 
----- Take 5: 
0 
1 
sum (0,1) 
1 
sum (0,1) 
sum (1,1) 
2 
sum (0,1) 
sum (0,1) 
sum (1,1) 
sum (1,2) 
3 

noti che quando abbiamo usato val:

  • Il " prendere 5 "non ha ricalcolato i valori calcolati dal" Take 4 ".
  • Il calcolo del 4 ° valore nel "Take 4" non ha comportato la ricalcolo del 3 ° valore.

Ma nessuno di questi è vero quando usiamo def. Ogni utilizzo di Stream, inclusa la propria ricorsione, inizia da zero con un nuovo Stream. Poiché produrre il valore Nth richiede che vengano prima prodotti i valori per N-1 e N-2, ognuno dei quali deve produrre i propri due predecessori e così via, il numero di chiamate a sum() richiesto per produrre un valore cresce molto come la sequenza di Fibonacci stessa: 0, 0, 1, 2, 4, 7, 12, 20, 33, .... E dal momento che tutti questi flussi sono allo stesso tempo, si esaurisce rapidamente la memoria.

Pertanto, date le scarse prestazioni e problemi di memoria, in genere non si desidera utilizzare def durante la creazione di un flusso.

Ma potrebbe darsi che in realtà do desideri un nuovo flusso ogni volta. Supponiamo che tu voglia un flusso di numeri interi casuali, e ogni volta che accedi al flusso desideri nuovi numeri interi, non una riproduzione di interi precedentemente calcolati. E quei valori precedentemente calcolati, dal momento che non si desidera riutilizzarli, occuperebbero inutilmente spazio sull'heap. In questo caso ha senso utilizzare def in modo da ottenere un nuovo flusso di volta in volta e non tenere su di esso, in modo che possa essere garbage collection:

scala> val randInts = Stream.continually(util.Random.nextInt(100)) 
randInts: scala.collection.immutable.Stream[Int] = Stream(1, ?) 

scala> (randInts take 1000).sum 
res92: Int = 51535 

scala> (randInts take 1000).sum 
res93: Int = 51535     <== same answer as before, from saved values 

scala> def randInts = Stream.continually(util.Random.nextInt(100)) 
randInts: scala.collection.immutable.Stream[Int] 

scala> (randInts take 1000).sum 
res94: Int = 49714 

scala> (randInts take 1000).sum 
res95: Int = 48442     <== different Stream, so new answer 

Fare randInts un metodo ci fa ottenere un nuovo flusso ogni volta, in modo da ottenere nuovi valori e il flusso può essere raccolto.

Si noti che ha senso usare def qui perché i nuovi valori non dipendono dai vecchi valori, quindi randInts non è definito in termini di se stesso.Stream.continually è un modo semplice per produrre flussi di questo tipo: basta dire come creare un valore e crea un flusso per te.

+0

Sei sicuro che sia una limitazione del compilatore di presentazione e non solo un campo rispetto a cosa variabile locale? –

+1

Buon punto, Luigi. Ho fatto ancora un po 'di sperimentazione dopo aver letto il tuo commento e ora non penso di capire completamente quale sia il problema, ma penso che sia legato al modo in cui quegli strumenti racchiudono il codice. Ottengo l'errore in un oggetto in un foglio di lavoro Scala, ma non in una classe, ed entrambi funzionano con Scalac. Modificheremo la risposta per non incolpare il PC. – AmigoNico

+0

Se lo avvolgi con un oggetto funziona perfettamente: http://www.scalakata.com/50975187e4b093f3524f3685 –