2012-11-04 9 views
10

In einer Antwort auf eine Frage Stackoverflow habe ich einen Strom als val, etwa so:Sollte ich beim Definieren eines Streams val oder def verwenden?

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

und jemand sagte mir, dass def sollte statt val verwendet werden, da Scala Kata klagt (wie auch das Scala-Arbeitsblatt in Eclipse), dass eine "Vorwärtsreferenz sich über die Definition von Wert s erstreckt."

Die Beispiele in den Stream-Dokumenten verwenden val. Welcher ist richtig?

Antwort

21

Scalac und die REPL sind mit diesem Code (mit val) in Ordnung, solange die Variable ein Feld einer Klasse und nicht eine lokale Variable ist. Sie können die Variable faul machen, um Scala Kata zu befriedigen, aber Sie würden in der Regel nicht def in einem echten Programm auf diese Weise verwenden (d. H. Einen Stream in Bezug auf sich selbst definieren). Wenn Sie dies tun, wird bei jedem Aufruf der Methode ein neuer Stream erstellt, sodass die Ergebnisse früherer Berechnungen (die im Stream gespeichert sind) nie wiederverwendet werden können. Wenn Sie viele Werte aus einem solchen Stream verwenden, ist die Leistung sehr schlecht und Sie haben möglicherweise keinen Speicher mehr.

Dieses Programm demonstriert das Problem mit def auf diese Weise mit:

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

Hier ist die Ausgabe:

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

Beachten Sie, dass, wenn wir gebraucht val:

  • Der " take 5 "hat die von" take 4 "berechneten Werte nicht neu berechnet.
  • Die Berechnung des 4. Wertes in "Take 4" hat nicht zur Folge, dass der 3. Wert neu berechnet wird.

Aber keiner von denen ist wahr, wenn wir Def verwenden. Jede Verwendung des Streams, einschließlich seiner eigenen Rekursion, beginnt bei Null mit einem neuen Stream. Da das Erzeugen des N-ten Wertes erfordert, dass wir zuerst die Werte für N-1 und N-2 erzeugen, von denen jedes seine eigenen zwei Vorgänger usw. erzeugen muss, wächst die Anzahl der Aufrufe von sum(), die zur Erzeugung eines Wertes benötigt werden, sehr ähnlich die Fibonacci-Folge selbst: 0, 0, 1, 2, 4, 7, 12, 20, 33, .... Und da alle diese Streams gleichzeitig auf dem Heap sind, ist der Speicher schnell erschöpft.

Aufgrund der schlechten Leistung und des Speicherproblems möchten Sie def normalerweise nicht verwenden, um einen Stream zu erstellen.

Aber es könnte sein, dass Sie eigentlich tun wollen jedes Mal einen neuen Stream. Angenommen, Sie möchten einen Stream mit zufälligen Ganzzahlen, und jedes Mal, wenn Sie auf den Stream zugreifen, möchten Sie neue Ganzzahlen und keine Wiederholung zuvor berechneter Ganzzahlen. Und diese zuvor berechneten Werte würden unnötigerweise Speicherplatz auf dem Heap belegen, da Sie sie nicht wiederverwenden wollen. In diesem Fall ist es sinnvoll def zu verwenden, so dass Sie einen neuen Stream jedes Mal bekommen und halten Sie nicht, um es auf, so dass es Garbage Collection sein kann:

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 

machen randInts ein Verfahren uns veranlasst, Erhalte jedes Mal einen neuen Stream, damit wir neue Werte erhalten und der Stream gesammelt werden kann.

Beachten Sie, dass es nur sinnvoll ist, def hier zu verwenden, da neue Werte nicht von alten Werten abhängen, also ist randInts nicht in sich selbst definiert.Stream.continually ist eine einfache Möglichkeit, solche Streams zu erzeugen: Sie sagen nur, wie man einen Wert erzeugt und es macht einen Stream für Sie.

+0

Sind Sie sicher, dass es sich um eine Einschränkung des Präsentationscompilers handelt und nicht nur um ein Feld vs. lokale Variable? –

+1

Guter Punkt, Luigi. Ich habe nach dem Lesen Ihres Kommentars noch etwas experimentiert und jetzt glaube ich nicht, dass ich das Problem vollständig verstehe, aber ich denke, es hängt damit zusammen, wie diese Tools den Code einbinden. Ich erhalte den Fehler in einem Objekt in einem Scala-Arbeitsblatt, aber nicht in einer Klasse, und beide arbeiten mit scalac. Ich werde die Antwort ändern, um den PC nicht zu beschuldigen. – AmigoNico

+0

Wenn Sie es mit einem Objekt einpacken, funktioniert es gut: http://www.scalakata.com/50975187e4b093f3524f3685 –

Verwandte Themen