2013-06-16 27 views
13

Scala cho phép đóng cửa nhưQuản lý bộ nhớ của bao đóng trong công việc Scala như thế nào?

def newCounter = { 
    var a=0 
() => {a+=1;a} 
} 

trong đó xác định một chức năng mà trên tất cả các cuộc gọi trả về một chức năng truy cập độc lập mới bắt đầu từ 1:

scala> val counter1 = newCounter 
counter1:() => Int = <function0> 

scala> counter1() 
res0: Int = 1 

scala> counter1() 
res1: Int = 2 

scala> val counter2 = newCounter 
counter2:() => Int = <function0> 

scala> counter2() 
res2: Int = 1 

scala> counter1() 
res3: Int = 3 

này là khá ấn tượng như thường a sẽ là một đại diện của địa chỉ bộ nhớ trên khung ngăn xếp của newCounter. Tôi vừa đọc chương đóng của "Lập trình trong Scala" và nó chỉ có những điều sau đây để nói về vấn đề đó (tr. 155):

Trình biên dịch Scala sắp xếp lại mọi thứ trong trường hợp như thế này sao cho tham số tồn tại trên heap, thay vì stack, và do đó có thể sống lâu hơn cuộc gọi phương thức đã tạo ra nó. Việc sắp xếp lại này được thực hiện một cách tự động, vì vậy bạn không phải lo lắng về nó.

Mọi người có thể giải thích cách hoạt động của tính năng này trên cấp mã byte không? Là truy cập tương tự như một biến thành viên của một lớp học với tất cả các đồng bộ hóa liên quan và tác động hiệu suất?

+2

Điều này được gọi là "vấn đề funarg" và tôi đoán wiki có thể có một số gợi ý về nền tảng lý thuyết: https://en.wikipedia.org/wiki/Funarg_problem. Các giải pháp chung dường như là "đặt hồ sơ kích hoạt hoặc các phần của nó trên heap". (Google cũng có thể tìm thấy một số bài giảng/ghi chú hoặc giấy tờ về điều này.) – millimoose

+3

Lưu ý SIP 21 "bào tử" (tên cuộc gọi!) Http://docs.scala-lang.org/sips/pending/spores.html –

+0

Câu hỏi liên quan: http://stackoverflow.com/questions/12831024/with-scala-closures-when-do-captured-variables-start-to-live-on-the-jvm-heap –

Trả lời

14

Bạn có thể sử dụng scalac -Xprint:lambdalift <scala-file-name> để điều tra điều này.

Mã của bạn thực sự là một cái gì đó như thế này:

def newCounter = { 
    val a: runtime.IntRef = new runtime.IntRef(0); 
    new Function0 { 
    private[this] val a$1 = a 
    def apply() = { 
     a$1.elem = a$1.elem + 1 
     a$1.elem 
    } 
    } 
} 

Có một wrapper cho bất kỳ var sử dụng bởi lambda. Khác vars (không được sử dụng trong bao đóng) là các biến miền địa phương phổ biến.

Liên kết tới trình bao bọc này được lưu trữ dưới dạng trường trong trường hợp hàm.

lambdalift trong -Xprint:lambdaliftcompiler phase. Bạn có thể nhận tất cả các giai đoạn với -Xshow-phases. Bạn có thể sử dụng số pha thay vì tên, nó hữu ích khi bạn không chắc chắn giai đoạn nào bạn cần.

+0

'20' là gì có nghĩa là trong cmd-line? – pedrofurla

+0

Btw, nó sẽ được quan tâm để tương phản với một biến bên ngoài mà không được sử dụng trong việc đóng cửa. – pedrofurla

+0

@pedrofurla: Tôi đã cập nhật câu trả lời của mình. '20' là quá mức cần thiết,' lambdalift' ('15') là đủ. – senia