2013-09-26 144 views
8

Tôi hiểu khái niệm cơ bản về từng cuộc gọi và giá trị theo từng cuộc gọi và tôi cũng đã xem xét một số ví dụ. Tuy nhiên, tôi không rõ lắm về thời điểm sử dụng tên gọi. Điều gì sẽ là một kịch bản thế giới thực trong đó gọi theo tên sẽ có lợi thế đáng kể hoặc đạt được hiệu suất so với loại cuộc gọi khác? Phương pháp tư duy chính xác để chọn loại cuộc gọi trong khi thiết kế phương pháp là gì?Khi nào sử dụng gọi theo tên và gọi theo giá trị?

+0

bạn có một ví dụ nào trong đầu không? – dax

Trả lời

16

Có rất nhiều địa điểm được gọi theo tên có thể đạt được hiệu suất hoặc thậm chí chính xác.

Ví dụ về hiệu suất đơn giản: ghi nhật ký. Hãy tưởng tượng một giao diện như thế này:

trait Logger { 
    def info(msg: => String) 
    def warn(msg: => String) 
    def error(msg: => String) 
} 

Và sau đó sử dụng như thế này:

logger.info("Time spent on X: " + computeTimeSpent) 

Nếu phương pháp info không làm gì cả (vì, nói rằng, mức độ khai thác gỗ đã được cấu hình cho cao hơn) , sau đó computeTimeSpent không bao giờ được gọi, tiết kiệm thời gian. Điều này xảy ra rất nhiều với logger, nơi người ta thường thấy thao tác chuỗi có thể tốn kém tương đối so với các nhiệm vụ được ghi lại.

Ví dụ về chính xác: toán tử logic.

Bạn có thể nhìn thấy mã như thế này:

if (ref != null && ref.isSomething) 

Giả sử bạn tuyên bố && phương pháp như thế này:

trait Boolean { 
    def &&(other: Boolean): Boolean 
} 

sau đó, bất cứ khi nào refnull, bạn sẽ nhận được một lỗi vì isSomething sẽ được gọi vào tham chiếu null trước khi được chuyển đến &&. Vì lý do này, khai báo thực tế là:

trait Boolean { 
    def &&(other: => Boolean): Boolean = 
    if (this) this else other 
} 

Vì vậy, người ta thực sự có thể thắc mắc là khi nào nên sử dụng giá trị theo từng cuộc gọi. Trong thực tế, trong ngôn ngữ lập trình Haskell, mọi thứ hoạt động tương tự như cách hoạt động của từng công trình gọi (tương tự, nhưng không giống nhau). Có những lý do chính đáng để không sử dụng tên gọi: nó chậm hơn, nó tạo ra nhiều lớp hơn (có nghĩa là chương trình mất nhiều thời gian hơn để tải), nó tiêu thụ nhiều bộ nhớ hơn và đủ khác nhau khiến nhiều người có lý do khó khăn về nó.

+0

Có một sửa đổi nhỏ ở đây trong mô tả ở trên: Trong ví dụ ngay cả khi mức nhật ký được đặt cao hơn mức thông tin, hàm được gọi bên trong trình ghi thông tin sẽ được gọi. Chỉ thông điệp nhật ký sẽ không được in. – Sangeeta

+0

@Sangeeta Không, hàm sẽ _không được gọi. Đó là toàn bộ vấn đề. –

4

Cách đơn giản nó có thể được giải thích là

gọi-by-giá trị chức năng tính toán thông qua trong giá trị biểu hiện của trước khi gọi hàm, do đó giá trị như nhau được truy cập mỗi thời gian. Tuy nhiên, chức năng gọi theo tên tính toán lại giá trị của biểu thức được thông qua mỗi khi được truy cập.

Tôi luôn nghĩ thuật ngữ này là không cần thiết khó hiểu. Một hàm có thể có nhiều thông số thay đổi theo trạng thái gọi theo tên và giá trị theo từng giá trị. Vì vậy, nó không phải là một hàm là call-by-name hoặc call-by-value, nó là mỗi tham số của nó có thể là pass-by-name hoặc pass-by-value. Hơn nữa, "gọi theo tên" không liên quan gì đến tên. => Int là một kiểu khác với Int; đó là "chức năng của không có đối số sẽ tạo ra một Int" vs chỉ Int. Một khi bạn đã có các hàm hạng nhất, bạn không cần phải phát minh ra thuật ngữ gọi theo tên để mô tả điều này.

6

Gọi theo tên có nghĩa là giá trị được đánh giá tại thời điểm nó được truy cập, trong khi với giá trị cuộc gọi được đánh giá đầu tiên và sau đó được chuyển đến phương pháp.

Để xem sự khác biệt, hãy xem xét ví dụ này (lập trình hoàn toàn không có chức năng chỉ có tác dụng phụ;)). Giả sử bạn muốn tạo một hàm đo số lượng thời gian mà một số thao tác thực hiện. Bạn có thể làm điều đó với cuộc gọi-by-name:

def measure(action: => Unit) = { 
    println("Starting to measure time") 
    val startTime = System.nanoTime 
    action 
    val endTime = System.nanoTime 
    println("Operation took "+(endTime-startTime)+" ns") 
} 

measure { 
    println("Will now sleep a little") 
    Thread.sleep(1000) 
} 

Bạn sẽ nhận được kết quả (YMMV):

Starting to measure time 
Will now sleep a little 
Operation took 1000167919 ns 

Nhưng nếu bạn chỉ thay đổi chữ ký của measure-measure(action: Unit) nên nó sử dụng pass- bởi giá trị, kết quả sẽ là:

Will now sleep a little 
Starting to measure time 
Operation took 1760 ns 

Như bạn có thể thấy, action được đánh giá trước khi measure thậm chí bắt đầu và cũng là thời gian trôi qua gần 0 do hành động đã được chạy trước khi phương thức được gọi.

Ở đây, giá trị theo thời gian cho phép hành vi mong muốn của phương pháp đạt được. Trong một số trường hợp, nó không ảnh hưởng đến tính chính xác, nhưng không ảnh hưởng đến hiệu suất, ví dụ trong khung ghi nhật ký, nơi một biểu thức phức tạp có thể không cần được đánh giá nếu kết quả không được sử dụng.

0

Khi tham số từng tên được sử dụng nhiều lần trong một hàm, thông số được đánh giá nhiều lần.

Khi tham số được truyền vào phải là cuộc gọi hàm thuần túy theo Lập trình hàm, mỗi đánh giá trong hàm được gọi sẽ luôn tạo ra cùng một kết quả. Do đó, gọi theo tên sẽ lãng phí hơn so với giá trị gọi theo giá trị thông thường.