2015-10-09 13 views
19

byłem kodowania w Scala i robi kilka szybkich refaktoryzacji w IntelliJ, gdy natknąłem się na następujący fragment tajemniczości ...Nulls in Scala ... dlaczego jest to możliwe?

package misc 

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

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

I wtedy zauważył, że kompilator nie narzekał, więc postanowiłem spróbować Aby uruchomić to i mam bardzo interesujące wyjście

Value is: null 
Process finished with exit code 0 

Czy ktoś może mi powiedzieć, dlaczego to działa?

EDIT:

  1. Pierwszy problem, nazwa wartość przypisany jest odniesienie do siebie mimo że jeszcze nie istnieje; dlaczego dokładnie kompilator Scala nie eksploduje z błędami ???

  2. Dlaczego wartość przydziału jest zerowa?

+0

w Scala 2.11.4 Mam wojnę ning 'warning: nazwa wartości w obiekcie WTF robi nic innego, jak wywoływanie samej nazwy rekursywnie: String = name' – Brian

+0

Używam Scala 2.11.7, i nie dostaję tego komunikatu i nie ma rekurencji, ponieważ jest to val: czy na pewno nie używasz * def *? Czy uruchamiasz go z wiersza poleceń lub z interpretera? Używam Intellij –

+0

Właśnie zauważyłem, że nie powiedzie się w REPL z następującym komunikatem .. scala> val o: String = o : 8: ostrzeżenie: wartość o nie wywołuje sam siebie rekursywnie –

Odpowiedz

14

1.) Dlaczego kompilator nie wybuchnąć

Oto ograniczonej przykładem. To kompiluje ponieważ przez danego rodzaju wartość domyślną można wywnioskować:

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 } 

nie skompilować, ponieważ nie ma wartości domyślne można wywnioskować:

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 Co się dzieje tutaj

moja interpretacja. Tak więc uważaj: Zasada jednolitego dostępu jest włączona. Przypisanie do val x wywołuje narzędzie dostępu x(), które zwraca unitialized wartość x. So x jest ustawione na wartość domyślną.

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.) Dlaczego wartość jest null

Wartości domyślne są określone przez środowisko Scala jest kompilowany do.

W podanym przykładzie wygląda na to, że uruchamiasz maszynę JVM. Wartością domyślną dla obiektu tutaj jest null.

Kiedy nie podasz wartości, używana jest jako wartość domyślna.

Wartości domyślne JVM:

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

Również wartość domyślna jest prawidłową wartością dla danego typu: Oto przykład w 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

W punkcie # 1 oznacza to, że Scala ma to jako cechę projektowania języka? Inicjalizacja z samo referencją, która nie została przypisana? –

+0

Kiedy dobrze pamiętam w Scali 2.10, był to błąd. Wygląda na to, że w jakiś sposób potrzebują tej funkcji w 2.11. –

+0

. Dlaczego inicjalizacja sama wywołuje wartość domyślną w zmiennej? Co więcej, dlaczego komunikat o błędzie mówi "call .. recursively", podczas gdy wartość jest wartością "Int", a nie funkcją? Wydaje mi się, że okazuje się, że jest nieskończenie rekursywny, nie znajdując żadnego terminalnego zmniejszenia wartości podczas jego oceny. –

11

dlaczego dokładnie robi Scala kompilator nie eksploduje z błędami?

Ponieważ ten problem nie może zostać rozwiązany w ogólnym przypadku. Czy znasz numer halting problem? Problem z zatrzymaniem mówi, że nie można napisać algorytmu, który dowiódłby, że program zostanie zatrzymany. Ponieważ problem ustalenia, czy definicja rekursywna spowodowałaby przypisanie zerowe, można zredukować do problemu zatrzymania, nie można go także rozwiązać.

Cóż, teraz jest to dość łatwe do zabronić rekurencyjne definicje w ogóle, to jest na przykład wykonywana dla wartości, które są żadne wartości klasy:

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" } 
           ^

Dla wartości klasowych ta funkcja nie jest zabronione z kilku powodów :

  • ich zakres nie jest ograniczony
  • JVM inicjuje je z domyślnej wartości (co jest null dla typów referencyjnych).
  • wartości rekurencyjne są użyteczne

Twój przypadek użycia jest trywialne, jak to:

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

Ale co o tym:

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 

Albo to:

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) 

Jak widać, dość łatwo jest pisać programy, w których nie jest już oczywiste, do jakiej wartości one doprowadzą. A ponieważ problemu nie można rozwiązać, nie możemy pozwolić, aby kompilator wykonał dla nas pracę w nieważnych przypadkach.

Oznacza to również, że przypadki trywialne, takie jak te przedstawione w pytaniu, mogą zostać zakodowane na stałe w kompilatorze. Ponieważ jednak nie może istnieć algorytm, który może wykryć wszystkie możliwe, trywialne przypadki, wszystkie przypadki, które kiedykolwiek zostały znalezione, muszą zostać zakodowane na stałe w kompilatorze (nie wspominając o tym, że definicja przypadku trywialnego nie istnieje). Dlatego nie byłoby rozsądnie nawet zacząć kodowanie niektórych z tych przypadków. W rezultacie spowolniłoby to kompilator i kompilator, który jest trudniejszy do utrzymania.

Można argumentować, że w przypadku użycia, który spala co sekundę użytkownika, rozsądnie jest przynajmniej zakodować tak ekstremalny scenariusz. Z drugiej strony, niektórzy ludzie muszą być spaleni, aby nauczyć się czegoś nowego. ;)

6

Myślę, że @Andreas 'answer ma już niezbędne informacje. Ja po prostu staram się zapewnić dodatkowe wyjaśnienie:

Kiedy piszesz val name: String = name na poziomie klasy, to robi kilka różnych rzeczy w tym samym czasie:

  • stworzyć pole name
  • utworzyć getter name()
  • utworzyć kod przypisania name = name, która staje się częścią podstawową konstruktora

Jest to jednoznacznie sformułowane przez Andreasa "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(); 
    () 
    } 
    } 
} 

Składnia nie jest Scala, to (jak sugeruje [[syntax trees at end of cleanup]]) tekstową reprezentację co kompilator będzie później przekonwertować do kodu bajtowego. Odkładając na bok nieznaną składnię, możemy to zinterpretować, tak jak JVM:

  • JVM tworzy obiekt. W tym momencie wszystkie pola mają wartości domyślne. val x: Int = _; jest jak int x; w Javie, czyli wartość domyślna JVM jest używany, który jest 0 dla I (tj int w Javie lub Int w Scala)
  • konstruktor nazywa dla obiektu
  • (super konstruktor nazywa)
  • konstruktor nazywa x()
  • x() powraca x, czyli 0
  • x jest przypisany 0
  • konstruktor zwraca

jak widać, po początkowym etapie analizowania, nie ma nic w drzewie składni, który wydaje się nie tak od razu, mimo że oryginalny kod źródłowy wygląda źle. Nie powiedziałbym, że jest to zachowanie się spodziewać, więc to sobie wyobrazić jedną z trzech rzeczy:

  • albo, deweloperów Scala widział to jako zbyt skomplikowany, aby rozpoznać i zabronić
  • lub, to regresja i po prostu nie został znaleziony jako błąd
  • lub, że jest to „cecha” i istnieje uzasadniona potrzeba takiego zachowania

(kolejność odzwierciedla moją opinię o wysokie prawdopodobieństwo, malejące)

+2

dzięki za przedłużenie odpowiedzi. –