2008-09-23 30 views
8

Sử dụng dòng lệnh REPL Scala của:ngữ nghĩa đệ quy quá tải trong Scala REPL - ngôn ngữ JVM

def foo(x: Int): Unit = {} 
def foo(x: String): Unit = {println(foo(2))} 

cho

error: type mismatch; 
found: Int(2) 
required: String 

Có vẻ như bạn không thể xác định quá tải phương pháp đệ quy trong REPL. Tôi nghĩ rằng đây là một lỗi trong Scala REPL và nộp nó, nhưng nó gần như ngay lập tức đóng cửa với "wontfix: Tôi không thấy bất kỳ cách nào có thể được hỗ trợ cho ngữ nghĩa của thông dịch viên, bởi vì hai phương pháp này phải được biên dịch cùng với nhau." Ông đề nghị đặt các phương pháp trong một đối tượng kèm theo.

Có triển khai ngôn ngữ JVM hay chuyên gia Scala có thể giải thích tại sao không? Tôi có thể thấy nó sẽ là một vấn đề nếu các phương pháp gọi nhau chẳng hạn, nhưng trong trường hợp này? Hoặc nếu câu hỏi này quá lớn và bạn nghĩ rằng tôi cần kiến ​​thức tiên quyết hơn, ai đó có bất kỳ liên kết tốt nào tới sách hoặc trang web về triển khai ngôn ngữ, đặc biệt là trên JVM không? (Tôi biết về blog của John Rose, và cuốn sách Lập trình Ngôn ngữ Pragmatics ... nhưng đó là về nó. :)

Trả lời

11

Sự cố là do thông dịch viên thường phải thay đổi thay thế các phần tử hiện có có tên nhất định, thay vì quá tải chúng. Ví dụ, tôi sẽ thường xuyên được chạy qua thử nghiệm với một cái gì đó, thường tạo ra một phương pháp gọi là test:

def test(x: Int) = x + x 

Một chút sau này, chúng ta hãy nói rằng tôi đang chạy một thí nghiệm khác nhau và tôi tạo ra một phương thức có tên test, không liên quan đến đầu tiên:

def test(ls: List[Int]) = (0 /: ls) { _ + _ } 

Đây không phải là một kịch bản hoàn toàn không thực tế. Trong thực tế, nó chính xác như thế nào hầu hết mọi người sử dụng thông dịch viên, thường xuyên mà không hề nhận ra nó. Nếu thông dịch viên tự ý quyết định giữ cả hai phiên bản test trong phạm vi, điều đó có thể dẫn đến sự khác biệt ngữ nghĩa khó hiểu khi sử dụng thử nghiệm. Ví dụ, chúng ta có thể thực hiện cuộc gọi đến test, vô tình đi qua một Int hơn List[Int] (không phải là tai nạn khó nhất trên thế giới):

test(1 :: Nil) // => 1 
test(2)   // => 4 (expecting 2) 

Theo thời gian, phạm vi gốc của thông dịch viên sẽ nhận được vô cùng lộn xộn với Tôi có xu hướng để người phiên dịch của tôi mở cửa trong nhiều ngày tại một thời điểm, nhưng nếu quá tải như thế này được cho phép, chúng tôi sẽ buộc phải "tuôn ra" thông dịch viên thường xuyên vì mọi thứ trở nên quá khó hiểu .

Nó không phải là một giới hạn của JVM hoặc trình biên dịch Scala, đó là một quyết định thiết kế có chủ ý. Như đã đề cập trong lỗi, bạn vẫn có thể quá tải nếu bạn đang ở trong một cái gì đó khác với phạm vi gốc. Kèm theo các phương pháp kiểm tra của bạn trong một lớp học có vẻ như là giải pháp tốt nhất cho tôi.

+0

Câu trả lời tuyệt vời Daniel, cảm ơn. Ngoài ra, tôi thích blog của bạn. :) –

4

REPL sẽ chấp nhận nếu bạn sao chép cả hai dòng và dán cả hai cùng một lúc.

5
% scala28 
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def foo(x: Int): Unit =() ; def foo(x: String): Unit = { println(foo(2)) } 
foo: (x: String)Unit <and> (x: Int)Unit 
foo: (x: String)Unit <and> (x: Int)Unit 

scala> foo(5) 

scala> foo("abc") 
() 
1

Như được hiển thị bởi extempore's câu trả lời, có thể quá tải. Daniel's nhận xét về quyết định thiết kế là chính xác, nhưng, tôi nghĩ, không đầy đủ và gây hiểu nhầm một chút. Không có ngoài vòng pháp luật quá tải (vì chúng có thể), nhưng chúng không dễ dàng đạt được.

Các quyết định thiết kế dẫn đến này là:

  1. Tất cả các định nghĩa trước đó phải có sẵn.
  2. Chỉ mã mới được biên dịch, thay vì biên dịch lại mọi thứ được nhập vào mọi lúc.
  3. Phải có khả năng xác định lại các định nghĩa (như Daniel đã đề cập).
  4. Có thể xác định các thành viên như vals và defs, không chỉ các lớp và đối tượng.

Vấn đề là ... làm thế nào để đạt được tất cả các mục tiêu này? Làm thế nào để chúng tôi xử lý ví dụ của bạn?

def foo(x: Int): Unit = {} 
def foo(x: String): Unit = {println(foo(2))} 

Bắt đầu với mục 4, A val hoặc def chỉ có thể được định nghĩa bên trong một class, trait, object hoặc package object. Vì vậy, REPL đặt các định nghĩa bên trong các đối tượng, như thế này (đại diện không thực tế!)

package $line1 { // input line 
    object $read { // what was read 
    object $iw { // definitions 
     def foo(x: Int): Unit = {} 
    } 
    // val res1 would be here somewhere if this was an expression 
    } 
} 

Bây giờ, do cách JVM hoạt động, khi bạn định nghĩa một trong số họ, bạn không thể mở rộng chúng. Bạn có thể, tất nhiên, biên dịch lại mọi thứ, nhưng chúng tôi đã loại bỏ điều đó. Vì vậy, bạn cần đặt nó ở một nơi khác:

package $line1 { // input line 
    object $read { // what was read 
    object $iw { // definitions 
     def foo(x: String): Unit = { println(foo(2)) } 
    } 
    } 
} 

Và điều này giải thích lý do tại sao ví dụ của bạn không bị quá tải: chúng được định nghĩa ở hai nơi khác nhau. Nếu bạn đặt chúng trong cùng một dòng, chúng sẽ được định nghĩa cùng nhau, điều này sẽ làm cho chúng quá tải, như được thể hiện trong ví dụ của extempore. Đối với các quyết định thiết kế khác, mỗi định nghĩa nhập gói mới và "res" từ các gói trước đó, và nhập khẩu có thể đổ bóng lẫn nhau, điều này làm cho nó có thể "xác định lại" nội dung.

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