使用惰性参数初始化 class 时 Scala 编译器的奇怪行为
Strange behavior of Scala compiler when initializing a class with a lazy argument
怎么可能第一个是正确的 Scala 代码而第二个甚至不能编译?
编译的那个
object First {
class ABC(body: => Unit) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}
这个不能在 Scala 2.11 和 2.12 上编译
object Second {
class ABC(body: => Int) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}
一点也不奇怪。我们来看第一个例子:
您声明您的 class ABC
以接收 returns Unit
的按名称传递的参数并且您认为此片段:
val x = new ABC {
a + b
}
正在传递 body
参数,它不是。真正发生的是:
val x = new ABC(()) { a + b }
如果你 运行 该代码你会看到 println(body)
打印 () 因为你没有为你的 body
参数,编译器允许它编译,因为正如 scaladoc 所述,只有 1 个 Unit
:
类型的值
Unit is a subtype of scala.AnyVal. There is only one value of type Unit, (), and it is not represented by any object in the underlying runtime system. A method with return type Unit is analogous to a Java method which is declared void.
由于只有一个值,编译器允许您省略它,它会填补空白。单例对象不会发生这种情况,因为它们不会扩展 AnyVal
。 Int
的默认值是 0
,Unit
的默认值是 ()
,因为只有这个值可用,编译器接受它。
来自 documentation:
If ee has some value type and the expected type is Unit, ee is converted to the expected type by embedding it in the term { ee; () }.
单例对象不会扩展 AnyVal
因此它们不会得到相同的对待。
当您使用如下语法时:
new ABC {
// Here comes code that gets executed after the constructor code.
// Code here can returns Unit by default because a constructor always
// returns the type it is constructing.
}
您只是在向构造函数体中添加内容,而不是传递参数。
第二个示例无法编译,因为编译器无法推断出 body: => Int
的默认值,因此您必须显式传递它。
结论
构造函数括号内的代码与传递参数不同。在相同的情况下它可能看起来相同,但这是由于 "magic".
您不能将单个参数传递给花括号中的构造函数,因为这将被解析为 defining an anonymous class。如果要这样做,还需要将大括号括在普通大括号中,如下所示:
new ABC({
a + b
})
至于为什么编译器会接受new ABC {a + b}
,解释有点复杂,出乎意料:
new ABC {...}
等同于 new ABC() {...}
new ABC()
可以解析为 new ABC(())
因为自动元组,这是规范中没有提到的解析器的一个特性,见 SI-3583 Spec doesn't mention automatic tupling。相同的功能导致以下代码编译没有错误:
def f(a: Unit) = {}
f()
def g(a: (Int, Int)) = {}
g(0,1)
请注意调用会产生警告(甚至您的原始示例也会):
Adaptation of argument list by inserting () has been deprecated: this is unlikely to be what you want.
警告是从 2.11 开始产生的,见问题SI-8035 Deprecate automatic () insertion。
怎么可能第一个是正确的 Scala 代码而第二个甚至不能编译?
编译的那个
object First {
class ABC(body: => Unit) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}
这个不能在 Scala 2.11 和 2.12 上编译
object Second {
class ABC(body: => Int) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}
一点也不奇怪。我们来看第一个例子:
您声明您的 class ABC
以接收 returns Unit
的按名称传递的参数并且您认为此片段:
val x = new ABC {
a + b
}
正在传递 body
参数,它不是。真正发生的是:
val x = new ABC(()) { a + b }
如果你 运行 该代码你会看到 println(body)
打印 () 因为你没有为你的 body
参数,编译器允许它编译,因为正如 scaladoc 所述,只有 1 个 Unit
:
Unit is a subtype of scala.AnyVal. There is only one value of type Unit, (), and it is not represented by any object in the underlying runtime system. A method with return type Unit is analogous to a Java method which is declared void.
由于只有一个值,编译器允许您省略它,它会填补空白。单例对象不会发生这种情况,因为它们不会扩展 AnyVal
。 Int
的默认值是 0
,Unit
的默认值是 ()
,因为只有这个值可用,编译器接受它。
来自 documentation:
If ee has some value type and the expected type is Unit, ee is converted to the expected type by embedding it in the term { ee; () }.
单例对象不会扩展 AnyVal
因此它们不会得到相同的对待。
当您使用如下语法时:
new ABC {
// Here comes code that gets executed after the constructor code.
// Code here can returns Unit by default because a constructor always
// returns the type it is constructing.
}
您只是在向构造函数体中添加内容,而不是传递参数。
第二个示例无法编译,因为编译器无法推断出 body: => Int
的默认值,因此您必须显式传递它。
结论
构造函数括号内的代码与传递参数不同。在相同的情况下它可能看起来相同,但这是由于 "magic".
您不能将单个参数传递给花括号中的构造函数,因为这将被解析为 defining an anonymous class。如果要这样做,还需要将大括号括在普通大括号中,如下所示:
new ABC({
a + b
})
至于为什么编译器会接受new ABC {a + b}
,解释有点复杂,出乎意料:
new ABC {...}
等同于new ABC() {...}
new ABC()
可以解析为new ABC(())
因为自动元组,这是规范中没有提到的解析器的一个特性,见 SI-3583 Spec doesn't mention automatic tupling。相同的功能导致以下代码编译没有错误:def f(a: Unit) = {} f() def g(a: (Int, Int)) = {} g(0,1)
请注意调用会产生警告(甚至您的原始示例也会):
Adaptation of argument list by inserting () has been deprecated: this is unlikely to be what you want.
警告是从 2.11 开始产生的,见问题SI-8035 Deprecate automatic () insertion。