程式扎記: [Scala 小學堂] Scala Gossic : 繼續深入 - 模式比對 (基本模式)

標籤

2016年7月9日 星期六

[Scala 小學堂] Scala Gossic : 繼續深入 - 模式比對 (基本模式)

Source From Here
基本模式
基本模式可以單獨運用,也可以彼此組合,以形成更複雜的模式。這將將介紹的基本模式包括了:
- 常數模式(Constant pattern)
- 萬用字元模式(Wildcard pattern)
- 建構式模式(Constructor pattern)
- 變數模式(Variable pattern)
- 型別模式(Typed pattern)

模式中最簡單的種類是 常數模式Constant pattern),你可以在 Scala 中寫下的字面常量(Literal)都可以作為模式比對,例如:
  1. def what(a:Any) = a match {  
  2.     case 10     => "整數"  
  3.     case 0.1    => "浮點數"  
  4.     case 'A'    => "字元"  
  5.     case true   => "布林值"  
  6.     case "text" => "字串"  
  7.     case Nil    => "空串列"  
  8.     case _      => "?"  
  9. }  
使用 match 運算式時,如果無法比對成功,會丟出 MatchError。你可以在最後的 case 放一個 _,這表示符合任何對象,這是 萬用字元模式Wildcard pattern)的一個應用。以下也是個 萬用字元模式 的運用,你只想知道傳入的是 Point 或不是 Point
  1. case class Point(x: Int, y: Int)  
  2.   
  3. def what(a: Any) = a match {  
  4.     case Point(_, _)   => "圓"  
  5.     case _             => "不是圓"  
  6. }  
  7.   
  8. println(what(Point(12)))   // 顯示圓  
  9. println(what(Point(34)))   // 顯示圓  
  10. println(what("圓?"))         // 顯示不是圓  
實際上,上面先使用 建構式模式Constructor pattern),看看傳入的物件是不是 Point 所建構,如果是的話,再進一步來到了 萬用字元模式,所以不在乎 x 或 y 值為何。接著來看看 變數模式Variable pattern)運用的一個例子:
  1. def what(i: Any) = i match {  
  2.     case 100        => "滿分"  
  3.     case 90         => "A"  
  4.     case something  => "不及格?" + something  
  5. }  
  6.   
  7. println(what(100))   // 滿分  
  8. println(what(90))    // A   
  9. println(what(80))    // 不及格?80  
在不是 100 或 90 的情況下,則符合最後的 case,而且會將比對的物件指定給 something 這個變數。這個例子看不出 變數模式 的實際運用,來看看這個例子:
  1. case class Point(x: Int, y: Int)  
  2.   
  3. def what(a: Any) = a match {  
  4.     case Point(x, y)   => "圓 (" + x + ", " + y + ")"  
  5.     case _             => "不是圓"  
  6. }  
  7.   
  8. println(what(Point(12)))   // 圓 (1, 2)  
  9. println(what(Point(34)))   // 圓 (3, 4)  
  10. println(what("圓?"))         // 不是圓  
上面先使用 建構式模式Constructor pattern),看看傳入的物件是不是 Point 所建構,如果是的話,再進一步將 Point 中的值分別指定給 x 與 y 變數,在 => 之中就可以直接取用 x 與 y 的值。在使用 常數模式 時,需注意別與 變數模式 混淆,例如你也許以為下面這個程式是 常數模式 比對:
  1. val x = 10  
  2. def what(i: Int) = i match {  
  3.     case x   => "10"  
  4.     case _   => "不是 10"  
  5. }  
  6.   
  7. println(what(10))  
  8. println(what(20))  
但事實是,你使用了 變數模式x 是 match 中的一個變數,而不是你在第一行所宣告的 x,上面的程式會有編譯警告:


變數模式 一定會先匹配到,所以之後的 萬用字元模式 永遠不會被匹配到。在 Scala 中,一個常數在命名時,首字母必須大寫,這不僅是慣例,也是在某些場合被認定為常數的要件。例如以下的程式就可得到預期結果:
  1. val X = 10  
  2. def what(i: Int) = i match {  
  3.     case X   => "10"  
  4.     case _   => "不是 10"  
  5. }  
  6. println(what(10))  // 10  
  7. println(what(20))  // 不是 10  
變數 X 是首字大寫,在 match 中會被認定為 常數模式,因此可以編譯成功並執行。再繼續來看到 建構式模式,它可以形成巢狀,例如:
  1. case class Point(x: Int, y: Int)  
  2. case class Circle(p: Point, r: Int)  
  3. case class Cylinder(c: Circle, h: Int)  
  4.   
  5. def what(a: Any) = a match {  
  6.     case Point(_, _)                         => "點"  
  7.     case Circle(Point(_, _), _)              => "圓"  
  8.     case Cylinder(Circle(Point(_, _), _), _) => "柱"  
  9. }  
  10.   
  11. println(what(Point(1010)))                            // 點  
  12. println(what(Circle(Point(1010), 10)))                // 圓  
  13. println(what(Cylinder(Circle(Point(1010), 10), 10)))  // 柱  
上例中使用了 建構式模式 與 萬用字元模式,以傳入 Cylinder 為例,會使用 建構式模式 比對 Cylinder,符合後再使用 建構式模式 比較內層的 Circle,符合後再使用 建構式比較更內層的 Point,最後使用 萬用字元 比對。

再來看到 型別模式Typed pattern),直接使用 重新定義 equals() 方法 中的一個例子作說明:
  1. class Point(val x: Int, val y: Int) {  
  2.     override def equals(a: Any) = a match {  
  3.         case that: Point => this.x == that.x && this.y == that.y  
  4.         case _ => false  
  5.     }  
  6.     override def hashCode = 41 * (41 + x) + y  
  7. }  
在第一個 case 中的比對中,傳入的物件型態必須符合 Point 型別,如果是的話,指定給 that 變數。這個例子如果不使用 型別模式,則你可以這麼撰寫:
  1. class Point(val x: Int, val y: Int) {  
  2.     override def equals(a: Any) = {  
  3.         if(a.isInstanceOf[Point]) {  
  4.             val that = a.asInstanceOf[Point]  
  5.             this.x == that.x && this.y == that.y  
  6.         }  
  7.         false  
  8.     }  
  9.       
  10.     override def hashCode = 41 * (41 + x) + y  
  11. }  
一般來說,不鼓勵直接進行型態檢查與型態轉換,寫來也比較冗長,建議還是採用模式匹配的方式。在使用 型別模式 時,若想匹配 ListSet、Map 等型態,可以使用以下的方式:
  1. def what(a: Any) = a match {  
  2.     case str : String    => "字串"  
  3.     case list: List[_]   => "串列"  
  4.     case set : Set[_]    => "集合"  
  5.     case map : Map[_, _] => "字典"  
  6.     case _               => "別的東西"  
  7. }  
  8.   
  9. println(what("text"))         // 字串  
  10. println(what(List(12)))     // 串列  
  11. println(what(Set(123)))   // 集合  
但是你沒辦法指匹配群集中別元素型態,例如:
  1. def what(a: Any) = a match {  
  2.     case list: List[String]  => "字串串列"  
  3.     case _                   => "別的東西"  
  4. }  
這在編譯時會出現警示訊息:
warning: there were unchecked warnings; re-run with -unchecked for details

理由在於,Scala 的泛型(Generic)採用的是 型別抹除Type erasure)的作法,加入群集後的物件基本上就失去型態資訊了(如果你熟悉 Java,這跟物件加入 Java 群集中意思是一樣的,所有的物件失去的型態資訊)。如果你執意運行以下的程式,結果將不正確:
  1. def what(a: Any) = a match {  
  2.     case list: List[String]  => "字串串列"  
  3.     case _                   => "別的東西"  
  4. }  
  5. val list1 = List("text")  
  6. val list2 = List(1)  
  7. println(what(list1))  // 字串串列  
  8. println(what(list2))  // 字串串列  
唯一的例外是陣列,陣列沒有採用型別抹除,因為陣列在 Scala 中處理的方式特意與 Java 中陣列相同,所以下面的寫法是可行的:
  1. def what(a: Any) = a match {  
  2.     case arr: Array[Int]     => "整數陣列"  
  3.     case arr: Array[String]  => "字串陣列"  
  4.     case _                   => "別的東西"  
  5. }  
  6.   
  7. val arr1 = Array(1)  
  8. val arr2 = Array("text")  
  9. println(what(arr1))  // 整數陣列  
  10. println(what(arr2))  // 字串陣列  


沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!