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

时间:2015-10-09 16:09:58

标签: 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")
}

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

Value is: null
Process finished with exit code 0

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

编辑:

  1. 第一个问题,值名称被分配了对自身的引用,即使它尚不存在;为什么完全 Scala编译器没有出现错误?

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

2 个答案:

答案 0 :(得分: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 }

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

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这里发生了什么

我的解释。所以要注意:统一访问原则开始了。 对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();
      ()
    }
  }
}                            ^

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.

默认值也是给定类型的有效值: 以下是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" }
                                 ^

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

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

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

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)

正如您所看到的那样,编写程序时很容易编写程序,这些程序不再显而易见。由于暂停问题无法解决,我们不能让编译器在非平凡的情况下为我们工作。

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

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