2012-11-04 40 views
10

在回答一個StackOverflow的問題,我創建了一個流作爲VAL,像這樣:定義流時,我應該使用val還是def?

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

,有人告訴我,高清應該用來代替VAL因爲Scala卡塔抱怨(如不Eclipse中的Scala工作表):「前向引用擴展了數值s的定義」。

但Stream文檔中的示例使用val。哪一個是對的?

回答

21

只要變量是類的字段而不是局部變量,Scalac和REPL就可以使用該代碼(使用val)。你可以讓變量懶惰來滿足Scala Kata,但是你通常不希望在一個真正的程序中以這種方式使用def(也就是說,根據它本身來定義Stream)。如果這樣做,每次調用方法時都會創建一個新的Stream,所以以前的計算結果(保存在Stream中)永遠不會被重用。如果你使用這樣一個Stream中的很多值,性能會很糟糕,最終你會耗盡內存。

該程序以這種方式使用高清演示了此問題:

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

這裏是輸出:

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

注意,當我們使用VAL:

  • 的「採取5「沒有重新計算由」採取4「計算的值。
  • 計算「take 4」中的第4個值不會導致重新計算第3個值。

但是,當我們使用def時,這兩者都不是真的。 Stream的每一次使用,包括它自己的遞歸,都是從頭開始用新的Stream開始的。由於產生第N個值需要我們首先產生N-1和N-2的值,其中每一個必須產生它自己的兩個前置值,依此類推,產生一個值所需的sum()斐波那契序列本身:0,0,1,2,4,7,12,20,33 ....因爲所有這些流同時在堆中,所以我們很快就會耗盡內存。

所以考慮到糟糕的性能和內存問題,您通常不希望在創建Stream時使用def。

但它可能是,你實際上每次都想要一個新的流。假設您想要一個隨機整數流,並且每次訪問Stream時都需要新整數,而不是以前計算整數的重播。而那些以前計算的值,因爲你不想重用它們,會不必要地佔用堆空間。在這種情況下,這是有道理的,讓你得到一個新的數據流的每個時間,不要抓住它以高清使用,以便它可以被垃圾收集:

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 

製作randInts的方法使我們每次獲得一個新的Stream,所以我們得到新的值,並且Stream可以被收集。

請注意,這裏只使用def是有意義的,因爲新值不依賴於舊值,所以randInts不是根據自身定義的。Stream.continually是生成這種Streams的簡單方法:您只需告訴它如何創建一個值,併爲您製作Stream。

+0

你確定它是一個演示編譯器的限制,而不只是一個字段與本地變量的東西? –

+1

好點,Luigi。在閱讀您的評論之後,我做了一些更多的嘗試,現在我不認爲自己完全理解問題是什麼,但我認爲它與這些工具包裝代碼的方式有關。我在Scala工作表中的一個對象中得到錯誤,但不是在一個類中,並且都與scalac一起工作。我會修改答案,不要責怪PC。 – AmigoNico

+0

如果你用一個對象包裝它,它的工作原理就很好:http://www.scalakata.com/50975187e4b093f3524f3685 –

相關問題