Scala 递归 val 行为

Scala recursive val behaviour

你认为打印出来的是什么?

val foo: String = "foo" + foo
println(foo)

val foo2: Int = 3 + foo2
println(foo2)

答案:

foonull
3

为什么?规范中是否有部分 describes/explains 这个?

编辑:为了澄清我的惊讶 - 我确实意识到 fooval foo: String = "foo" + foo 处未定义,这就是为什么它具有默认值 null(整数为零)。但这似乎不太"clean",我看到这里的意见很赞同我。我希望编译器能阻止我做那样的事情。它在某些特定情况下确实有意义,例如在定义 Streams 时,它们本质上是惰性的,但对于字符串和整数,我希望要么因为重新分配给 val 而停止我,要么告诉我我正在尝试使用未定义的值,就像我写 val foo = whatever 一样(假定 whatever 从未定义)。

为了使事情更加复杂,@dk14 指出这种行为只存在于以字段表示的值中,不会发生在块中,例如

val bar: String = {
  val foo: String = "foo" + foo // error: forward reference extends...
  "bar"
}

Scala 的 Int 由底层的原始类型 (int) 支持,其默认值(初始化前)为 0。

另一方面,String 是一个 Object,未初始化时确实是 null

是的,请参阅 SLS Section 4.2

foo 是一个 String ,它是一个引用类型。它的默认值为 nullfoo2 是一个 Int,默认值为 0。

在这两种情况下,您指的是一个尚未初始化的 val,因此使用默认值。

在这两种情况下 foo resp。 foo2 根据 JVM 规范具有默认值。对于引用类型是 null,对于 intInt0 - 正如 Scala 拼写的那样。

不幸的是,由于与 Java 的 OOP 模型 ("Object-oriented meets Functional") 的妥协,scala 不如我们所说的 Haskell 安全。 Scala 有一个安全的子集,叫做 Scalazzi,sbt/scalac-plugins 中的一些可以给你更多 warnings/errors:

https://github.com/puffnfresh/wartremover (doesn't check 对于你找到的情况)

回到你的案例,这只发生在以字段表示的值上,当你在 function/method/block:

中时不会发生
scala> val foo2: Int = 3 + foo2 
foo2: Int = 3

scala> {val foo2: Int = 3 + foo2 }
<console>:14: error: forward reference extends over definition of value foo2
       {val foo2: Int = 3 + foo2 }
                            ^

scala> def method = {val a: Int = 3 + a}
<console>:12: error: forward reference extends over definition of value a
       def method = {val a: Int = 3 + a}

这种情况的原因是与 Java 集成,因为 val 编译到 JVM 中的 final 字段,因此 Scala 保存了 JVM 类 的所有初始化细节(我相信它是 JSR-133 的一部分:Java 内存模型)。这是解释此行为的更复杂的示例:

 scala> object Z{ val a = b; val b = 5}
 <console>:12: warning: Reference to uninitialized value b
   object Z{ val a = b; val b = 5}
                     ^
 defined object Z

 scala> Z.a
 res12: Int = 0

所以,在这里你可以看到你一开始没有看到的警告。