Scala中的空...为什么这可能?

Abi*_*oso 19 scala intellij-idea

我在Scala编写代码并在Intellij中做了一些快速重构,当时我偶然发现了下面的一些奇怪...

package misc

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

  val name: String = name
  println(s"Value is: $name")
}
Run Code Online (Sandbox Code Playgroud)

然后我注意到编译器没有抱怨,所以我决定尝试运行它,我得到了一个非常有趣的输出

Value is: null
Process finished with exit code 0
Run Code Online (Sandbox Code Playgroud)

谁能告诉我为什么这样有效?

编辑:

  1. 第一个问题,值名称被赋予对自身引用,即使它尚不存在; 为什么恰好没有Scala编译器不会有错误的爆炸???

  2. 为什么赋值的值为null?

And*_*ann 14

1.)为什么编译器不会爆炸

这是一个简化的例子.这是编译因为通过给定类型可以推断出默认值:

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 }
Run Code Online (Sandbox Code Playgroud)

这不会编译,因为不能推断出默认值:

class ExampleDoesNotCompile { def x = x }

scalac ExampleDoesNotCompile.scala 
ExampleDoesNotCompile.scala:1: error: recursive method x needs result type
class ExampleDoesNotCompile { def x = x }
Run Code Online (Sandbox Code Playgroud)

1.1这里发生了什么

我的解释.所以要注意:统一访问原则启动.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();
      ()
    }
  }
}                            ^
Run Code Online (Sandbox Code Playgroud)

2.)为什么值为null

默认值由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.
Run Code Online (Sandbox Code Playgroud)

此外,默认值是给定类型的有效值:以下是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
Run Code Online (Sandbox Code Playgroud)


kir*_*uku 11

为什么Scala编译器没有错误爆炸?

因为这个问题在一般情况下无法解决.你知道停止问题吗?暂停问题说,不可能编写一个算法来查明程序是否会停止.由于发现递归定义是否会导致空分配的问题可以减少到暂停问题,因此也无法解决它.

好吧,现在很容易禁止递归定义,例如,对于没有类值的值,这样做:

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" }
                                 ^
Run Code Online (Sandbox Code Playgroud)

对于类值,出于以下几个原因,不禁止使用此功能:

  • 它们的范围不受限制
  • JVM使用默认值(对于引用类型为null)初始化它们.
  • 递归值很有用

您的用例很简单,就像这样:

scala> val k: String = k+"abc"
k: String = nullabc
Run Code Online (Sandbox Code Playgroud)

但是这个怎么样:

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
Run Code Online (Sandbox Code Playgroud)

或这个:

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)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,编写程序非常容易,因为程序不再明显,它们将产生哪些价值.由于暂停问题无法解决,我们不能让编译器在非平凡的情况下为我们工作.

这也意味着,在您的问题中显示的那些简单的案例可以在编译器中进行硬编码.但由于不存在可以检测所有可能的微不足道案例的算法,所以发现的所有案例都需要在编译器中进行硬编码(更不用说不存在简单案例的定义).因此,即使开始对其中一些案例进行硬编码也是不明智的.最终会导致编译器速度变慢,编译器难以维护.

有人可能会争辩说,对于烧毁每一个用户的用例,至少硬编码这样一种极端情况是明智的.另一方面,有些人只需要被焚烧以便学习新东西.;)


Sil*_*eak 6

我认为@Andreas的答案已经有了必要的信息.我将尝试提供其他说明:

当你val name: String = name在班级写作时,这会同时做一些不同的事情:

  • 创造这个领域 name
  • 创造吸气剂 name()
  • 为赋值创建代码name = name,它成为主构造函数的一部分

这就是安德烈亚斯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();
      ()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

语法不是Scala,它(如建议的那样[[syntax trees at end of cleanup]])是编译器稍后将转换为字节码的文本表示.除了一些不熟悉的语法之外,我们可以解释这个,就像JVM一样:

  • JVM创建一个对象.此时,所有字段都具有默认值.val x: Int = _;就像int x;在Java中,即使用JVM的缺省值,它是0用于I(即intJava或IntScala中)
  • 为对象调用构造函数
  • (超级构造函数被调用)
  • 构造函数调用 x()
  • x()返回x,这是0
  • x 被安排了 0
  • 构造函数返回

正如您所看到的,在初始解析步骤之后,语法树中没有任何内容似乎立即出错,即使原始源代码看起来不对.我不会说这是我期望的行为,所以我想象三件事之一:

  • 或者,Scala开发人员认为它过于复杂而无法识别和禁止
  • 或者,这是一个回归,并没有被发现是一个错误
  • 或者,这是一个"功能",这种行为是合法的需要

(排序反映了我对降序的可能性的看法)

  • 谢谢你扩展答案. (2认同)