在定义函数之前调用函数(前向引用扩展到变量的定义)

时间:2014-03-01 00:48:50

标签: function scala variables compiler-construction scope

考虑这个基本的Scala示例代码:

object Test {
    def main(args: Array[String]) {
        inner()

        var x: Int = 5

        def inner() {
            println("x: " + x)
        }
    }
}

尝试编译它会产生以下错误消息:

test.scala:3: error: forward reference extends over definition of variable x
        inner()
        ^
one error found

问题:

  1. 在此上下文中什么是前向引用,它对“扩展变量x的定义”意味着什么?
  2. 为什么前面的代码会引发编译时错误?
  3. 如何捕获此错误?好像编译器必须实现一些类似解释器的功能并遵循函数调用!
  4. 这个问题并不是关于定义的顺序,而是确切地调用函数的时候。 在定义函数之前调用函数是完全合法的 - 但如果在调用和函数定义之间放置一个变量,它会突然变为非法,并且函数使用此变量。

    我想解释这个语言功能!为什么会这样?它是如何工作的?还有其他一些更复杂的例子 - 即它只是某些其他特征的一部分还是某些规则的结果?

    我想象编译器目前在做什么:

    1. 检查函数是否是可以访问当前作用域变量的闭包,
    2. 检查它是否确实访问当前范围内的变量,
    3. 对于闭包访问的每个变量,检查变量是否在调用之前定义
    4. 我基本上回答了我的第三个问题吗?这是这种行为的工作原理吗?它似乎使编译器复杂化(特别是如果我们考虑具有多级函数的情况)。

      如果是这种情况,它如何融入语言的正式定义,即语法?在我看来,我写的程序在语法上是正确的。

2 个答案:

答案 0 :(得分:5)

来自http://www.scala-lang.org/docu/files/ScalaReference.pdf

声明或定义引入的名称范围是整个声明 包含结合的序列。但是,对前进有限制 块中的引用:在语句序列中s1 ... sn构成块,如果是简单的话 si中的名称是指由sj定义的实体,其中j> = i,然后是和之间的所有sk 包括si和sj,

•sk不能是变量定义。

•如果sk是一个值定义,它必须是懒惰的

答案 1 :(得分:3)

此错误消息表示不允许在块中进行前向引用。在一个块中,所有变量(或值)必须以线性顺序定义。

请注意,类或对象中允许使用前向引用,但不允许使用块(方法定义)。例如:

这将有效:

object Test { // forward reference is allowed in an object
  def inner() {
    println("x: " + x)
  }
  var x: Int = 5
}

这也有效:

class Test { // forward reference is allowed in an class
  def inner() {
    println("x: " + x)
  }
  var x: Int = 5
}

但这些都不会起作用:

def main() { // forward reference for vals or vars is not allowed in a block 
  inner()

  def inner() {
    println("x: " + x)
  }

  var x: Int = 5
}

def main() {
  inner()

  var x: Int = 5

  def inner() {
    println("x: " + x)
  }
}

由于val“x”是字段初始化语句,因此在初始化之前引用它是非法的(“x”在初始化之前包含null)。

为了使其正常工作,您可以将var更改为惰性值:

def main() { // forward reference for lazy vals is allowed in a block 
  def inner() {
    println("x: " + x)
  }

  lazy val x: Int = 5
}

在惰性val表达式之前调用调用lazy val的方法的一个合法示例:

def main() { 
  inner()

  def inner() {
    println("x: " + x)
  }

  lazy val x: Int = 5
}

如果没有“x”是懒惰的,“x”将被立即初始化,这会破坏Scala编译器执行的重新检查阶段。

对于这种行为背后的一点理论,你可以看一下:https://wiki.scala-lang.org/display/SIW/Overview+of+Compiler+Phases

的“阶段重新检查”部分