2015-10-09 15 views
19

tôi đã mã hóa trong Scala và làm một số refactoring nhanh chóng trong Intellij, khi tôi stumbled khi các mảnh sau weirdness ...Nulls ở Scala ... tại sao điều này lại có thể xảy ra?

package misc 

/** 
* Created by abimbola on 05/10/15. 
*/ 
object WTF extends App { 

    val name: String = name 
    println(s"Value is: $name") 
} 

Sau đó tôi nhận thấy rằng trình biên dịch không phàn nàn, vì vậy tôi quyết định cố gắng để chạy điều này và tôi có một kết quả rất thú vị

Value is: null 
Process finished with exit code 0 

Bất cứ ai có thể cho tôi biết lý do tại sao lại hoạt động?

EDIT:

  1. Vấn đề đầu tiên, giá trị tên được gán một tham chiếu cho chính nó mặc dù nó chưa hề tồn tại; tại sao chính xác trình biên dịch Scala không phát nổ với các lỗi ???

  2. Tại sao giá trị của chuyển nhượng rỗng?

+0

trong Scala 2.11.4 Tôi chiến tranh ning 'warning: value name trong đối tượng WTF không có gì khác hơn là gọi chính nó đệ quy val name: String = name' – Brian

+0

Tôi đang sử dụng Scala 2.11.7, và tôi không nhận được thông báo đó và không có đệ quy vì nó là một val: bạn có chắc bạn không sử dụng * def *? Ngoài ra, bạn có đang chạy nó từ dòng lệnh hoặc trình thông dịch không ??? Tôi đang sử dụng Intellij –

+0

Tôi chỉ nhận thấy rằng nó không thành công trong REPL với thông báo sau .. scala> val o: String = o : 8: cảnh báo: giá trị o không có gì khác hơn là gọi chính nó đệ quy –

Trả lời

14

1.) Tại sao các trình biên dịch không phát nổ

Dưới đây là một ví dụ giảm. Đây biên dịch vì thông qua loại nhất định một giá trị mặc định có thể được suy ra:

class Example { val x: Int = x } 

scalac Example.scala 
Example.scala:1: warning: value x in class Example does nothing other than call itself recursively 
class Example { val x: Int = x } 

này không biên dịch vì không có giá trị mặc định có thể được suy ra:

class ExampleDoesNotCompile { def x = x } 

scalac ExampleDoesNotCompile.scala 
ExampleDoesNotCompile.scala:1: error: recursive method x needs result type 
class ExampleDoesNotCompile { def x = x } 

1,1 gì xảy ra ở đây

giải thích của tôi. Vì vậy hãy cẩn thận: Các nguyên tắc truy cập thống nhất đá. Việc chuyển giao cho các val x gọi accessor x() mà trả về giá trị đơn vị hóa của x. Vì vậy x được đặt thành giá trị mặc định.

class Example { val x: Int = x } 
          ^
[[syntax trees at end of     cleanup]] // Example.scala 
package <empty> { 
    class Example extends Object { 
    private[this] val x: Int = _; 
    <stable> <accessor> def x(): Int = Example.this.x; 
    def <init>(): Example = { 
     Example.super.<init>(); 
     Example.this.x = Example.this.x(); 
    () 
    } 
    } 
}       ^

2.) Tại sao giá trị là null

Các giá trị mặc định được xác định bởi môi trường Scala được biên soạn để.

Trong ví dụ bạn đã cho, có vẻ như bạn chạy trên JVM. Giá trị mặc định cho đối tượng ở đây là null.

Vì vậy, khi bạn không cung cấp giá trị, giá trị mặc định được sử dụng làm dự phòng.

Mặc định giá trị JVM:

byte 0 
short 0 
int 0 
long 0L 
float 0.0f 
double 0.0d 
char '\u0000' 
boolean false 
Object null // String are objects. 

Ngoài giá trị mặc định là một giá trị hợp lệ cho loại nhất định: Dưới đây là một ví dụ trong REPL:

scala> val x : Int = 0 
x: Int = 0 

scala> val x : Int = null 
<console>:10: error: an expression of type Null is ineligible for implicit conversion 
val x : Int = null 
       ^
scala> val x : String = null 
x: String = null 
+0

Ở # 1, điều này có nghĩa là Scala có tính năng này như một tính năng thiết kế ngôn ngữ? Khởi tạo với một tham chiếu tự nó chưa được gán? –

+0

Khi tôi nhớ chính xác trong Scala 2.10 đây là lỗi. Dường như họ bằng cách nào đó cần tính năng đó trong 2.11 –

+0

Tại sao việc khởi tạo với chính nó tạo ra một giá trị mặc định vào biến? Ngoài ra, tại sao thông báo lỗi nói "gọi .. đệ quy" trong khi giá trị là một 'Int' chứ không phải là một giá trị hàm? Tôi đã nghĩ rằng nó hóa ra là vô hạn đệ quy, không tìm thấy giảm thiết bị đầu cuối cho một giá trị trong quá trình đánh giá của nó. –

11

tại sao chính xác hiện Scala trình biên dịch không phát nổ với lỗi?

Vì sự cố này không thể giải quyết được trong trường hợp chung. Bạn có biết số halting problem không? Các vấn đề dừng lại nói rằng nó không thể viết một thuật toán mà phát hiện ra nếu một chương trình bao giờ dừng lại. Kể từ khi vấn đề tìm ra nếu một định nghĩa đệ quy sẽ dẫn đến một chuyển nhượng null có thể được giảm đến vấn đề dừng, nó cũng không thể giải quyết nó.

Vâng, bây giờ nó là khá dễ dàng để cấm các định nghĩa đệ quy ở tất cả, đây là ví dụ làm cho các giá trị mà không có giá trị lớp:

scala> def f = { val k: String = k+"abc" } 
<console>:11: error: forward reference extends over definition of value k 
     def f = { val k: String = k+"abc" } 
           ^

Đối với giá trị đẳng cấp tính năng này không cấm đối với một vài lý do :

  • phạm vi của họ là không giới hạn
  • Các JVM khởi chúng với giá trị mặc định (mà là null với nhiều loại tài liệu tham khảo).
  • giá trị Đệ quy là hữu ích

trường hợp sử dụng của bạn là tầm thường, như là thế này:

scala> val k: String = k+"abc" 
k: String = nullabc 

Nhưng những gì về vấn đề này:

scala> object X { val x: Int = Y.y+1 }; object Y { val y: Int = X.x+1 } 
defined object X 
defined object Y 

scala> X.x 
res2: Int = 2 

scala> Y.y 
res3: Int = 1 

scala> object X { val x: Int = Y.y+1 }; object Y { val y: Int = X.x+1 } 
defined object X 
defined object Y 

scala> Y.y 
res4: Int = 2 

scala> X.x 
res5: Int = 1 

Hoặc này:

scala> val f: Stream[BigInt] = 1 #:: 1 #:: f.zip(f.tail).map { case (a,b) => a+b } 
f: Stream[BigInt] = Stream(1, ?) 

scala> f.take(10).toList 
res7: List[BigInt] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55) 

Như bạn có thể thấy nó khá dễ dàng để viết các chương trình mà nó không còn rõ ràng nữa với giá trị mà chúng sẽ tạo ra. Và kể từ khi vấn đề dừng lại không thể giải quyết được, chúng tôi không thể để trình biên dịch thực hiện công việc cho chúng tôi trong các trường hợp không tầm thường.

Điều này cũng có nghĩa là các trường hợp tầm thường, như trường hợp được hiển thị trong câu hỏi của bạn, có thể được mã hóa cứng trong trình biên dịch. Nhưng vì không thể tồn tại một thuật toán có thể phát hiện tất cả các trường hợp tầm thường, tất cả các trường hợp được tìm thấy cần phải được mã hóa cứng trong trình biên dịch (chưa kể rằng định nghĩa của một trường hợp tầm thường không tồn tại). Vì vậy nó sẽ không được khôn ngoan để thậm chí bắt đầu hardcoding một số trường hợp này. Cuối cùng nó sẽ dẫn đến trình biên dịch chậm hơn và trình biên dịch khó bảo trì hơn.

Người ta có thể lập luận rằng đối với một trường hợp sử dụng có thể đốt cháy mọi người dùng thứ hai, thì ít nhất cũng nên mã hóa một kịch bản cực đoan như vậy. Mặt khác, một số người chỉ cần bị đốt cháy để học cái gì đó mới. ;)

6

Tôi nghĩ @Andreas 'answer đã có thông tin cần thiết. Tôi sẽ chỉ cố gắng để cung cấp thêm giải thích:

Khi bạn viết val name: String = name ở cấp lớp, điều này một vài điều khác nhau cùng một lúc:

  • tạo lĩnh vực name
  • tạo getter name()
  • tạo mã cho việc giao name = name, mà trở thành một phần của các nhà xây dựng chính

Đây là những gì được làm rõ bởi Andreas '1.1

package <empty> { 
    class Example extends Object { 
    private[this] val x: Int = _; 
    <stable> <accessor> def x(): Int = Example.this.x; 
    def <init>(): Example = { 
     Example.super.<init>(); 
     Example.this.x = Example.this.x(); 
    () 
    } 
    } 
} 

Cú pháp không phải là Scala, nó là (theo đề nghị của [[syntax trees at end of cleanup]]) một đại diện văn bản về những gì các trình biên dịch sau này sẽ chuyển đổi thành bytecode. Một số cú pháp không quen thuộc sang một bên, chúng ta có thể giải thích điều này, giống như JVM sẽ:

  • JVM tạo một đối tượng. Tại thời điểm này, tất cả các trường đều có giá trị mặc định. val x: Int = _; là như int x; trong Java, tức là giá trị mặc định của JVM được sử dụng, đó là 0 cho I (tức int trong Java, hoặc Int trong Scala)
  • constructor được gọi là cho các đối tượng
  • (các nhà xây dựng siêu được gọi là)
  • constructor gọi x()
  • x() lợi nhuận x, đó là 0
  • x được gán 0
  • các nhà xây dựng trả

như bạn có thể thấy, sau khi bước phân tích ban đầu, không có gì trong cây cú pháp mà dường như ngay lập tức sai, mặc dù mã nguồn gốc trông sai. Tôi sẽ không nói rằng đây là hành vi tôi mong đợi, vì vậy tôi sẽ tưởng tượng một trong ba điều:

  • Hoặc, các devs Scala thấy nó như là quá phức tạp để nhận biết và cấm
  • hay, đó là một hồi quy và chỉ đơn giản là không được tìm thấy như một lỗi
  • hay, đó là một "tính năng" và có nhu cầu chính đáng cho hành vi này

(đặt hàng phản ánh quan điểm của tôi về likeliness, theo thứ tự giảm dần)

+2

cảm ơn bạn đã mở rộng câu trả lời. –

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