Khi viết một hàm hoạt động trên Stream
(s), có các khái niệm khác nhau về đệ quy. Ý nghĩa đơn giản đầu tiên là không đệ quy vào mức độ biên dịch, vì đuôi nếu không được đánh giá ngay lập tức để hàm trả về ngay lập tức, nhưng dòng trở lại là đệ quy:Làm thế nào để viết chức năng đệ quy đuôi không bị rò rỉ bằng cách sử dụng Stream.cons trong Scala?
final def simpleRec[A](as: Stream[A]): Stream[B] =
if (a.isEmpty) Stream.empty
else someB(a.head) #:: simpleRec(a.tail)
Khái niệm trên của đệ quy không gây ra bất kỳ vấn đề . Điều thứ hai là thực sự đuôi-đệ quy vào mức độ biên dịch:
@tailrec
final def rec[A](as: Stream[A]): Stream[B] =
if (a.isEmpty) Stream.empty // A) degenerated
else if (someCond) rec(a.tail) // B) tail recursion
else someB(a.head) #:: rec(a.tail) // C) degenerated
Vấn đề ở đây là các trường hợp C)
được phát hiện bởi trình biên dịch như một cuộc gọi không tailrec, thậm chí nếu không có cuộc gọi thực tế thực hiện. Điều này có thể tránh được bằng cách tính toán đuôi của luồng vào chức năng trợ giúp:
Trong khi biên dịch, phương pháp này cuối cùng dẫn đến rò rỉ bộ nhớ. Kể từ khi đệ quy đuôi rec
cuối cùng được gọi từ hàm recHelp
, khung ngăn xếp của hàm recHelp
giữ tham chiếu đến đầu hơi nước và không để luồng được thu thập cho đến khi trả về cuộc gọi rec
, có thể khá dài (về các bước đệ quy) tùy thuộc vào số lượng cuộc gọi đến B)
. Lưu ý rằng ngay cả trong trường hợp helperless, nếu trình biên dịch cho phép @ tailrec, rò rỉ bộ nhớ vẫn có thể có mặt vì đuôi luồng lười sẽ có hiệu lực tạo một đối tượng ẩn danh giữ tham chiếu đến đầu luồng.
Cũng thấy http://stackoverflow.com/questions/12486762/scala-tail-recursive-stream-processor-function-defined-in-trait-holds-reference – ron
bất kỳ cơ hội nào của một đoạn mã hoạt động? I E. một cái mà OOM? – Chris
Chris: chắc chắn, so sánh hai: https://gist.github.com/3769565 (2.10.0-M7 không oom cho ok.scala) – ron