閱讀60 返回首頁    go 技術社區[雲棲]


話說模式匹配(4) scala裏的賦值語句都是模式匹配嗎?

先拋個問題,下麵的語句是否都合理(編譯通過),哪些會引起模式匹配?

scala> val a = 100 
scala> val A = 100 
scala> val a@b = 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 a@b = 100 是什麼意思?回憶一下第一篇裏講過的“變量綁定模式”,當時的例子有點複雜,重新理解一下:

//給"hello"字符串對象用v1這個變量名
scala> "hello" match { case v1 => println(v1) }

//變量綁定模式,把變量v2 綁定在v1這個模式上
scala> "hello" match { case v2@v1 => println(v2) }

上麵的例子中,第一行中v1是個變量模式。 第二行中v2是一個新的變量,隻有在v1這個模式匹配成功的情況下,才會把自己綁定到v1上,而v1因為是一個變量模式,它總能匹配成功,所以這裏v2也 會綁定到”hello”對象上。變量綁定模式通常不會這麼使用,更多用在綁定到一個複合結構的模式上,如:

scala> List(1,List(2,3)) match { case List(_, x@List(2,_*)) => println(x.size) }
2

把變量x綁定到了嵌套的 List(2,3) 這個對象上

但賦值語句val a@b = 100 跟上麵的有關係麼?我們通過ToolBox看看它”脫糖”後的語法樹:

scala> tb.parse("val a@b=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 = Test$@3042dc22

簡直逆天,難道這個背後也與模式匹配有關係麼?

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沒問題?假設對單個變量賦值也是模式匹配,那豈不無法定義大寫的變量了;肯定是有區別的,但又怎麼區分的?

我最初遇到這個困惑,在郵件列表裏問了這個問題,得到了一些回複,並且有人給了一個老帖子鏈接說早就討論過val 1=2這個話題了:https://thread.gmane.org/gmane.comp.lang.scala.user/44036

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


文章轉自 並發編程網-ifeve.com

最後更新:2017-05-22 20:03:17

  上一篇:go  Java中Redis的使用教程
  下一篇:go  Java包