話說模式匹配(4) scala裡的賦值語句都是模式匹配嗎?
先拋個問題,下面的語句是否都合理(編譯通過),哪些會引起模式匹配?
scala> val a = 100 scala> val A = 100 scala> val [email protected] = 100 scala> val (a,b) = (100,200) scala> val (a,B) = (100,200) //第二個變數大寫 scala> val Array(a,b) = Array(100,200) scala> val Array(a,B) = Array(100,200) scala> object Test { val 2 = 2 } scala> object Test { val 2 = 3 }
我們先看看其他語言(對scala有影響的)有關賦值語句的定義:
1) 在 ML 語言裡,對賦值語句的定義:
val P = E
表示定義了模式P
中的變數,並賦予它們表示式E
中相應的值。
2) 在Erlang中等號 = 表示一個模式匹配操作
在這兩種語言中,賦值語句都明確的定義為模式匹配,那麼scala中,所有的賦值語句是否都是模式匹配呢?
尤其scala可以說在函式式風格上與ML(及其家族)語言有某種血緣,在這一點上是否也與ML完全一致呢?
先分析一下上面的每條賦值語句:val a = 100
和 val A = 100
是直觀且沒有歧義的。
val [email protected] = 100
//給"hello"字串物件用v1這個變數名
scala> "hello" match { case v1 => println(v1) }
//變數繫結模式,把變數v2 繫結在v1這個模式上
scala> "hello" match { case [email protected] => println(v2) }
上面的例子中,第一行中v1是個變數模式。 第二行中v2是一個新的變數,只有在v1這個模式匹配成功的情況下,才會把自己繫結到v1上,而v1因為是一個變數模式,它總能匹配成功,所以這裡v2也會繫結到”hello”物件上。變數繫結模式通常不會這麼使用,更多用在繫結到一個複合結構的模式上,如:
scala> List(1,List(2,3)) match { case List(_, [email protected](2,_*)) => println(x.size) }
2
把變數x繫結到了巢狀的 List(2,3) 這個物件上
但賦值語句val [email protected] = 100
跟上面的有關係麼?我們通過ToolBox看看它”脫糖”後的語法樹:
scala> tb.parse("val [email protected]=100")
res13: tb.u.Tree =
{
<synthetic> private[this] val x$3 = 100: @scala.unchecked match {
case (a @ (b @ _)) => scala.Tuple2(a, b) //這一句
};
val a = x$3._1;
val b = x$3._2
}
有註釋的那一句裡面把a,b兩個區域性變數繫結到萬用字元”_”上,而這個萬用字元模式case _ =>
可以匹配任何物件,所以相當於把a,b兩個變數繫結到了100這個物件上,併產生了一個二元組記錄這兩個區域性變數值。最終把二元組裡的值分別賦給了我們定義的a,b兩個變數。
接下來的val (a,b) = (100,200)
這個賦值也容易理解,把二元組裡的值分別賦給a,b兩個變數麼,也是經過模式匹配的麼?繼續用ToolBox分析:
scala> tb.parse("val (a,b)=(100,200)")
res14: tb.u.Tree =
{
<synthetic> private[this] val x$4 = scala.Tuple2(100, 200): @scala.unchecked match {
case scala.Tuple2((a @ _), (b @ _)) => scala.Tuple2(a, b)
};
val a = x$4._1;
val b = x$4._2
}
看到了,是一個構造器模式與變數繫結模式的混合模式匹配。
再下一個val (a,B) = (100,200)
這個與上一個有區別麼?回顧一下第一篇裡講到的“常量模式”:當變數大寫時將被對待為常量模式,也就是說 大寫B 和上面的 小寫b 是兩種不同的模式!!
scala> tb.parse("val (a,B)=(100,200)")
res15: tb.u.Tree =
val a = scala.Tuple2(100, 200): @scala.unchecked match {
case scala.Tuple2((a @ _), B) => a
}
大寫B在這裡當作常量來解析,但又找不到B這個變數(除非之前有定義過),就報錯了:
scala> val (a,B) = (100,200)
<console>:8: error: not found: value B
val (a,B) = (100,200)
^
後邊兩個Array的賦值語句與這兩個類似,小括號寫法只是元組(Tuple)的語法糖而已。
最後,真正有趣,且會讓新手崩潰的情況 object Test { val 2 = 2 }
為什麼這個編譯和初始化都沒問題?
scala> object Test { val 2 = 2 }
defined module Test
scala> Test
res16: Test.type = [email protected]
簡直逆天,難道這個背後也與模式匹配有關係麼?
scala> tb.parse(" object Test { val 2 = 2 }")
res0: tb.u.Tree =
object Test extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
<synthetic> private[this] val x$1 = 2: @scala.unchecked match {
case 2 => ()
}
}
確實又是一個常量模式匹配,2匹配2,成功。
同理,下一個 object Test { val 2 = 3 }
也是個常量模式匹配,但為何明顯不匹配,卻可以編譯時成功,而執行時時才報錯呢?
scala> object Test { val 2 = 3 }
defined module Test
scala> Test
scala.MatchError: 3 (of class java.lang.Integer)
at Test$.<init>(<console>:8)
這是因為object 是惰性初始化的原因(lazy),如下:
// 對下面的單例
object Test { val a = 2 }
$ scalac -Xprint:jvm A.scala
package <empty> {
object Test extends Object {
private[this] val a: Int = _;
<stable> <accessor> def a(): Int = Test.this.a;
def <init>(): Test.type = {
Test.super.<init>();
Test.this.a = 2; //在初始化時才對成員賦值
()
}
}
}
在對多個變數賦值,或變數中有@符合,導致模式匹配還好理解,但”2=2″也引起模式匹配就會讓我產生疑問:
是否所有的賦值語句都是模式匹配?
為了驗證,通過編譯選項檢視val a=2
這樣對單個變數的賦值卻沒有看到模式匹配。
另外,如果單個變數也是模式匹配,為何大寫字母val A=2
沒問題?假設對單個變數賦值也是模式匹配,那豈不無法定義大寫的變量了;肯定是有區別的,但又怎麼區分的?
在那個帖子裡,martin也回覆了為何 val 1=2
是模式匹配,並且為何不把這種情況作為錯誤給修復掉:
A value definition is of the form
val <pattern> = <expression> // 這個同ML和Erlang語言
1 is a <pattern>
There is one edge case:
If the pattern is a single variable (upper or lower case or backquoted), then it is always treated as a variable, not a constant. Otherwise, there would be no way to define such a value.只有一種邊緣情況:如果模式是一個單獨的變數(大寫、小寫、或用反引號引起來的),那麼它總被當作變數,而非常量。否則就沒法定義這樣的一個值。
所以1=2
, "a"="b"
這樣的賦值語句雖然是一個變數,但變數名稱不符合上面的約束,產生了模式匹配。至於為何不修復這個問題(直接在編譯時報錯),也可以從這個帖子的線索中找到原因。