2012-11-04 28 views
10

Trong một câu trả lời cho một câu hỏi StackOverflow Tôi tạo ra một Stream như một val, như vậy:Tôi có nên sử dụng val hoặc def khi xác định Luồng không?

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

và ai đó nói với tôi rằng def nên được sử dụng thay vì val vì Scala Kata phàn nàn (cũng như Bảng tính Scala trong Eclipse) rằng "tham chiếu chuyển tiếp mở rộng trên định nghĩa của các giá trị s".

Nhưng các ví dụ trong tài liệu Luồng sử dụng val. Cái nào là đúng?

Trả lời

21

Scalac và REPL là tốt với mã đó (sử dụng val) miễn là biến là trường của một lớp chứ không phải là biến cục bộ. Bạn có thể làm cho biến trở nên thỏa mãn để thỏa mãn Scala Kata, nhưng bạn thường không muốn sử dụng def theo cách này (có nghĩa là, def a Stream về chính nó) trong một chương trình thực. Nếu bạn làm như vậy, một Stream mới sẽ được tạo ra mỗi khi phương thức được gọi, do đó kết quả của các tính toán trước đó (được lưu trong Stream) không bao giờ có thể được tái sử dụng. Nếu bạn sử dụng nhiều giá trị từ Luồng như vậy, hiệu suất sẽ rất khủng khiếp và cuối cùng bạn sẽ hết bộ nhớ.

Chương trình này chứng tỏ vấn đề với việc sử dụng def theo cách này:

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

Đây là kết quả:

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

Chú ý rằng khi chúng ta sử dụng val:

  • Các " mất 5 "không tính toán lại các giá trị được tính bằng" take 4 ".
  • Tính toán giá trị thứ 4 trong "take 4" không gây ra giá trị thứ 3 được tính toán lại.

Nhưng không ai trong số đó là đúng khi chúng tôi sử dụng def. Mỗi lần sử dụng Luồng, bao gồm cả đệ quy của chính nó, bắt đầu từ đầu bằng luồng mới. Vì việc tạo ra giá trị Nth yêu cầu đầu tiên chúng ta tạo ra các giá trị cho N-1 và N-2, mỗi giá trị phải tạo ra hai giá trị tiền nhiệm của chính nó và như vậy, số lượng các cuộc gọi đến tổng() cần thiết để tạo ra một giá trị bản thân dãy Fibonacci: 0, 0, 1, 2, 4, 7, 12, 20, 33, .... Và vì tất cả các luồng đó đều nằm trên heap cùng một lúc, chúng ta nhanh chóng hết bộ nhớ.

Vì vậy, với hiệu suất kém và các vấn đề về bộ nhớ, bạn thường không muốn sử dụng def trong việc tạo Luồng.

Nhưng có thể bạn thực sự là làm muốn một Luồng mới mỗi lần. Giả sử bạn muốn một Dòng các số nguyên ngẫu nhiên và mỗi lần bạn truy cập Luồng bạn muốn các số nguyên mới, không phải phát lại các số nguyên được tính trước đây. Và những giá trị được tính trước đó, vì bạn không muốn sử dụng lại chúng, sẽ chiếm không gian trên heap một cách không cần thiết. Trong trường hợp đó nó làm cho cảm giác sử dụng def để bạn có được một Stream mới mỗi lần và không giữ cho nó, để nó có thể được thu gom rác:

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 

Làm randInts một phương pháp làm cho chúng ta có một Luồng mới mỗi lần, vì vậy chúng tôi nhận được các giá trị mới và Luồng có thể được thu thập.

Lưu ý rằng nó chỉ có ý nghĩa khi sử dụng def ở đây vì các giá trị mới không phụ thuộc vào các giá trị cũ, do đó randInts không được định nghĩa theo chính nó.Stream.continually là một cách dễ dàng để tạo ra các luồng như vậy: bạn chỉ cần nói cho nó cách tạo ra một giá trị và nó tạo ra một luồng cho bạn.

+0

Bạn có chắc đó là giới hạn trình biên dịch trình bày và không chỉ là trường so với biến cục bộ? –

+1

Điểm tốt, Luigi. Tôi đã làm một số thử nghiệm nhiều hơn sau khi đọc bình luận của bạn và bây giờ tôi không nghĩ rằng tôi hiểu hoàn toàn vấn đề là gì nhưng tôi nghĩ rằng nó liên quan đến cách những công cụ quấn mã. Tôi nhận được lỗi trong một đối tượng trong một bảng tính Scala, nhưng không phải trong một lớp học, và cả hai làm việc với scalac. Tôi sẽ sửa đổi câu trả lời để không đổ lỗi cho PC. – AmigoNico

+0

Nếu bạn quấn nó lên với một đối tượng nó hoạt động tốt: http://www.scalakata.com/50975187e4b093f3524f3685 –

Các vấn đề liên quan