1. 程式人生 > >話說模式匹配(4) scala裡的賦值語句都是模式匹配嗎?

話說模式匹配(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 = 100val 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" 這樣的賦值語句雖然是一個變數,但變數名稱不符合上面的約束,產生了模式匹配。至於為何不修復這個問題(直接在編譯時報錯),也可以從這個帖子的線索中找到原因。