我在Scala编写代码并在Intellij中进行了一些快速重构,当时我偶然发现了以下一些奇怪的内容......
package misc
/**
* Created by abimbola on 05/10/15.
*/
object WTF extends App {
val name: String = name
println(s"Value is: $name")
}
然后我注意到编译器没有抱怨,所以我决定尝试运行它,我得到了一个非常有趣的输出
Value is: null
Process finished with exit code 0
有谁能告诉我为什么这样有效?
编辑:
第一个问题,值名称被分配了对自身的引用,即使它尚不存在;为什么完全 Scala编译器没有出现错误?
为什么赋值的值为null?
答案 0 :(得分:14)
这是一个简化的例子。这是编译因为通过给定类型可以推断出默认值:
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 }
这不会编译,因为无法推断出默认值:
class ExampleDoesNotCompile { def x = x }
scalac ExampleDoesNotCompile.scala
ExampleDoesNotCompile.scala:1: error: recursive method x needs result type
class ExampleDoesNotCompile { def x = x }
我的解释。所以要注意:统一访问原则开始了。
对val x
的赋值调用访问者x()
,它返回x的单位化值。
所以x被设置为默认值。
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();
()
}
}
} ^
默认值由Scala编译为的环境决定。
在您给出的示例中,您看起来就像在JVM上运行一样。此处Object的默认值为null
。
因此,当您不提供值时,默认值将用作后备。
默认值JVM:
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
boolean false
Object null // String are objects.
默认值也是给定类型的有效值: 以下是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
答案 1 :(得分:11)
为什么Scala编译器不会因错误而爆炸?
因为在一般情况下无法解决这个问题。你知道halting problem吗?暂停问题说,不可能编写一个算法来查明程序是否会停止。由于查明递归定义是否会导致空赋值的问题可以简化为暂停问题,因此也无法解决它。
嗯,现在根本不容易禁止递归定义,例如对没有类值的值进行此操作:
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" }
^
对于类值,不会出于以下原因禁止使用此功能:
您的用例很简单,就像这样:
scala> val k: String = k+"abc"
k: String = nullabc
但是这个怎么样:
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
或者这个:
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)
正如您所看到的那样,编写程序时很容易编写程序,这些程序不再显而易见。由于暂停问题无法解决,我们不能让编译器在非平凡的情况下为我们工作。
这也意味着,如您的问题所示,可以在编译器中对简单的案例进行硬编码。但由于不存在可以检测所有可能的微不足道案例的算法,所以发现的所有案例都需要在编译器中进行硬编码(更不用说不存在普通案例的定义)。因此,即使开始对其中一些案例进行硬编码也是不明智的。它最终会导致编译器速度变慢,编译器难以维护。
有人可能会争辩说,对于烧毁每一个用户的用例,至少硬编码这种极端情况是明智的。另一方面,有些人只需要被焚烧以便学习新东西。 ;)