2014年3月18日 星期二

Scala basic5, Pattern matching

前言
程式邏輯的控制流程, 大部分都是從if else 起手, 可是當遇到複雜的資料結構要分析, 用底層的簡單判斷式就有點捉襟見肘, 複雜的流程設計可以透過switch來建置, 而Pattern match又更上一層樓的可以處理各種使用者自定義的資料結構, 實際上這也是來自於Functional programming的設計
先從基本的JAVA switch來初步認識Scala 的 Pattern match有哪些語法上的差別
JAVA switch 去找出輸入的數字次序
public class Ordinal {
  public static void main(String[] args) { 
    ordinal(Integer.parseInt(args[0]));
  }
  public static void ordinal(int number) {
    switch(number) {
      case 1: System.out.println("1st"); break;
      case 2: System.out.println("2nd"); break;
      case 3: System.out.println("3rd"); break;
      case 4: System.out.println("4th"); break;
      case 5: System.out.println("5th"); break;
      case 6: System.out.println("6th"); break;
      case 7: System.out.println("7th"); break;
      case 8: System.out.println("8th"); break;
      case 9: System.out.println("9th"); break;
      case 10: System.out.println("10th"); break;
      default : System.out.println("Cannot do beyond 10");
    }
  }
}
Scala 版的Pattern match 去找出輸入的數字次序
public class Ordinal {
ordinal(args(0).toInt)
def ordinal(number:Int) = number match {
  case 1 => println("1st")
  case 2 => println("2nd")
  case 3 => println("3rd")
  case 4 => println("4th")
  case 5 => println("5th")
  case 6 => println("6th")
  case 7 => println("7th")
  case 8 => println("8th")
  case 9 => println("9th")
  case 10 => println("10th")
  case _ => println("Cannot do beyond 10")
}
基本流程上都一樣, 取得來自command line的 args 字串並轉換成數字,來做比對看看該數字的次序, 在Scala 版的Pattern match 除了 match Keyword 外還有幾個一眼可觀察到的語法差異

  • 每個case結尾不必加上break, Scala Pattern match不像JAVA switch沒有break的話會執行到後續的case
  • default case在 Scala Pattern match中用 底線 _ 取代, 底線 _ 在Scala中有很多種用法, 這邊又是一點, 這樣用法很類似宣告變數的default value 用法


錯誤處理

當pattern match找不到相對應的case時候, Scala compiler會出現match error, 如下
 2 match { case 1 => "One" }
scala.MatchError: 2
    at .<init>(<console>:5)
    at .<clinit>(<console>)
當你的要比對的對象沒有符合的case, 就會出現match error, 在JAVA的儘管刪去default case且比對不到, JAVA還是繼續執行下去且沒有回傳任何東西, 相較之下, Scala的作法會比較安全的多


更複雜的資料結構比對

在JAVA的 switch 中只能處理primitive type與enums, 但Scala Pattern match進化到可以比對字串, 資料結構, 類型, 變數, 常數 以及建構子等複雜的資料結構,


Type match

def printType(obj: AnyRef) = obj match {
  case s: String => println("This is string")
  case l: List[_] => println("This is List")
  case a: Array[_] => println("This is an array")
  case d: java.util.Date => println("This is a date")
}
在上例中, printType 接收一個物件為參數, 並判斷出它的類型是甚麼, 透過 obj 原本的類型來找出相對應的case, 當一但比對到, obj 的值就會綁定到該類型case中的變數, 比如 obj 原本是字串類型, 那麼會對應到 case s : String, 並且把obj 的值綁定到變數 s 上,  在case的 => 後面的處理程序如果需要用到obj 就call s變數就可以了, 儘管這樣的類型比對在JAVA透過instanceof 與 casting的操作也可以達到, 但很顯然的Scala 版本簡潔的多,

BTW, AnyRef 物件是Scala中所有自定義物件的次祖先(等同JAVA中的 Object), 它再上去就是祖先Any 物件, 是所有物件的祖先, 關於Scala的Class hierarchy 在後續會繼續說明


Infix pattern match

在Scala Pattern match中也可以操做Infix(中序)的Pattern, 在 Infix(中序)結構中, 運算值會放在運算符號的左右兩邊, 就如同 2 + 2; +是運算符號, 2是運算值,  Infix(中序)結構最常見的就是List pattern, 如下面範例
List(1, 2, 3, 4) match { 
        case f :: s :: rest => List(f, s) 
        case _ => Nil 
      }
//return List[Int] = List(1, 2)
:: (兩個冒號)method 可以用來串接List, 因此 f :: s 就是一個Infix(中序)結構
由運算結構得知 :: 是中序運算符號, f & s 是運算值,  f :: s 就是把f & s串成一個List
而整個Pattern  f :: s :: rest 就是把整個 List(1, 2, 3, 4) 拆成第一個元素f , 加上第二個元素s , 加上剩下的成為一個 rest List (要注意f or s 各別都是Any類型), 因此 List(1, 2, 3, 4)就會變成 1 :: 2 ::  List( 3,4 ), 其中 f = 1 , s = 2, rest =  List( 3,4), 另一個理解方式就是反過來思考 List(1, 2, 3, 4)就是由 rest 串列(List(3, 4))透過 :: method去加上 f = 1 & s = 2 這兩個元素所組成的

更細節的Pattern match應用, 會在Case class時候進一步說明


待續

沒有留言:

張貼留言