2012-03-21 27 views
35

Trong tệp Parsers.scala (Scala 2.9.1) từ thư viện trình kết hợp phân tích cú pháp, tôi dường như đã gặp một tính năng Scala ít được biết đến hơn là "đối số lười". Dưới đây là một ví dụ:Đối số lười biếng của Scala: Làm thế nào để chúng hoạt động?

def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument 
    (for(a <- this; b <- p) yield new ~(a,b)).named("~") 
} 

Rõ ràng, có điều gì đó xảy ra ở đây với sự phân công của lập luận q gọi-by-tên cho val lười biếng p.

Cho đến nay tôi chưa thể tìm ra điều này và tại sao lại hữu ích. Có ai giúp được không?

+1

Bạn đã đặt bất kỳ nỗ lực trong việc cố gắng tìm hiểu chính mình? Đó là một phần của ngôn ngữ Scala và tìm kiếm trên internet sẽ tiết lộ đủ số lần truy cập vào 'scala lazy'. – ziggystar

+1

@ziggystar: Tôi đã thực hiện 2-3 tìm kiếm trên Google và không thể tìm thấy bất kỳ điều gì hữu ích. Lập luận lười biếng đã được đề cập trên một số yêu cầu tính năng Scala, nhưng không có lời giải thích nào mà tôi có thể hiểu được đã được đưa ra ở đó. –

+1

@ziggystar: Yêu cầu tính năng có tại đây: https://issues.scala-lang.org/browse/SI-240. Ngoài ra, tìm kiếm 'scala lazy' hoặc thậm chí' scala lazy argument' dường như không mang lại nhiều thông tin hữu ích bởi vì bạn hầu như sẽ nhận được kết quả về những thứ cơ bản hơn như lazy val và call-by-name. –

Trả lời

76

Đối số từng tên được gọi là mỗi khi bạn yêu cầu chúng. Vals lười biếng được gọi là lần đầu tiên và sau đó giá trị được lưu trữ. Nếu bạn yêu cầu lại, bạn sẽ nhận được giá trị được lưu trữ.

Do đó, một mô hình như

def foo(x: => Expensive) = { 
    lazy val cache = x 
    /* do lots of stuff with cache */ 
} 

là thỏa off-việc-as-dài-như-thể-và-chỉ-do-it-once mẫu cuối cùng. Nếu đường dẫn mã của bạn không bao giờ đưa bạn đến cần x thì nó sẽ không bao giờ được đánh giá. Nếu bạn cần nó nhiều lần, nó sẽ chỉ được đánh giá một lần và được lưu trữ để sử dụng trong tương lai. Vì vậy, bạn thực hiện cuộc gọi đắt tiền bằng không (nếu có thể) hoặc một lần (nếu không), được đảm bảo.

+1

+1, tôi đã giả định rằng tên gọi bằng một số tương đương với lười biếng, nhưng không phải ở tất cả các trường hợp! Cảm ơn bạn đã làm rõ ... – virtualeyes

+0

aka gọi theo nhu cầu – JPC

20

Các bài viết wikipedia cho Scala thậm chí trả lời những gì từ khóa lazy làm:

Sử dụng từ khóa lười biếng trì hoãn việc khởi tạo một giá trị cho đến khi giá trị này được sử dụng.

Ngoài ra, những gì bạn có trong mẫu mã này với q : => Parser[U] là thông số theo từng tên. Một tham số được khai báo theo cách này vẫn chưa được đánh giá, cho đến khi bạn đánh giá nó một cách rõ ràng ở đâu đó trong phương thức của bạn.

Dưới đây là một ví dụ từ REPL scala về cách các thông số cuộc gọi-by-tên làm việc:

scala> def f(p: => Int, eval : Boolean) = if (eval) println(p) 
f: (p: => Int, eval: Boolean)Unit 

scala> f(3, true) 
3 

scala> f(3/0, false) 

scala> f(3/0, true) 
java.lang.ArithmeticException:/by zero 
    at $anonfun$1.apply$mcI$sp(<console>:9) 
    ... 

Như bạn có thể thấy, 3/0 không được đánh giá ở tất cả trong cuộc gọi thứ hai. Kết hợp giá trị lười với tham số gọi tên như kết quả ở trên theo nghĩa sau: tham số q không được đánh giá ngay lập tức khi gọi phương thức. Thay vào đó, nó được gán cho giá trị lười biếng p, cũng không được đánh giá ngay lập tức. Chỉ sau này, khi sử dụng p điều này dẫn đến việc đánh giá là q. Nhưng, như p là một thông số val, q sẽ chỉ được đánh giá sau khi và kết quả được lưu trữ trong p để sử dụng lại sau này trong vòng lặp.

Bạn có thể dễ dàng nhìn thấy trong repl, rằng việc đánh giá nhiều có thể xảy ra trường hợp:

scala> def g(p: => Int) = println(p + p) 
g: (p: => Int)Unit 

scala> def calc = { println("evaluating") ; 10 } 
calc: Int 

scala> g(calc) 
evaluating 
evaluating 
20 
Các vấn đề liên quan