这个问题最初是从https://issues.scala-lang.org/browse/SI-4939发现的
请看如下的代码片段:
scala> object Foo1 { val 1 = 2 }
defined module Foo
scala> object Foo2 { val 1 = 1; def x = 1 }
defined module O
scala> Foo2.x
res1: Int = 1
scala> object Foo3 { val 1 = 2; def x = 1 }
defined module Foo3
scala> Foo3.x
scala.MatchError: 2
at Foo3$.<init>(<console>:5)
at Foo3$.<clinit>(<console>)
at .<init>(<console>:7)
at .<clinit>(<console>)
at RequestResult$.<init>(<console>:9)
是不是觉得有点不可思议?且慢,我们把Foo3
的定义放到一个Foo3.scala
文件中,并用scalac带上参数-Xprint:typer
进行编译后发现,val 1 =2
被scala编译器解释为:
<synthetic> private[this] val x$1: Unit = (2: Int(2) @unchecked) match {
case 1 => ()
}
还是无法理解?回忆一下模式匹配的用法:
scala> val (x,y)=(1,2)
x: Int = 1
y: Int = 2
scala> val (x,y,z)=(1,2,3)
x: Int = 1
y: Int = 2
z: Int = 3
是的, 在scala中等号=
可以用来做模式匹配,val (x,y) = (1,2)
事实上相当于 (1,2) match { case (x,y) => ()
明白了这个道理后,我们考虑一种特殊情况(没错,也许你已经想到),那就是val x = 2
。平时我们会把这条语句理解为“将数值2赋值给变量x”。没错,大多数命令式编程语言都提供这样的赋值语句,但在函数式编程语言中,这又何尝不是一种模式匹配的实例呢?因此,我们最好还是把val x = 2
理解为2 match { case x =>()}
吧。如果你想通了,那么对val 1 =2
就不再惊讶了。是的,val 1 =2
相当于做了一次模式匹配运算,即2 match { case 1 => () }
。显而易见,执行这个模式匹配操作会抛出scala.MatchError错误。
现在我们回过头来分析Foo1,Foo2, Foo3的定义和执行结果。 Foo1就不用再解释了。Foo2和 Foo3都定义了一个函数x, 但是当调用Foo3.x的时候出现错误,而Foo2.x却一切正常。显然是由于val 1 =2 导致 Foo3.x 调用时出现错误,但是有人可能会问,Foo3.x 不就返回一个数值1嘛,跟scala.MatchError有什么关系? 别急,我们再调用Foo3.x看看:
scala> Foo3.x
java.lang.NoClassDefFoundError: Could not initialize class Foo3$
at .<init>(<console>:9)
at .<clinit>(<console>)
at .<init>(<console>:11)
at .<clinit>(<console>)
at $export(<console>)
此时的错误变成java.lang.NoClassDefFoundError了,再调用一次Foo3.x,还是这个错。注意观察异常堆栈。看到了吗?对,初始化失败。 Foo3在初始化的时候会执行val 1 =2
,根据刚才的分析,这里实际上是做了一次模式匹配运算,由于匹配失败,所以抛出scala.MatchError的异常,并终止了Foo3的初始化过程,从而导致Could not initialize class Foo3$
这就是函数式语言的魅力,简洁,抽象,一致。她彻底颠覆了传统命令式编程语言的"赋值"的概念,把“模式匹配”发挥到淋漓尽致。