程式扎記: [Scala 小學堂] Scala Gossic : 繼續深入 - 提取器 (apply() 與 unapply() 方法)

標籤

2016年7月15日 星期五

[Scala 小學堂] Scala Gossic : 繼續深入 - 提取器 (apply() 與 unapply() 方法)

apply() 與 unapply() 方法
如果你可以定義 案例類別Case class),那麼你可以運用 模式比對 的特性,完成如下的變數指定動作:
  1. case class Apple(price: Int, weight: Int)  
  2.   
  3. val apple = Apple(1020)  
  4. val Apple(p, w) = apple  
  5. println(p)    // 10  
  6. println(w)    // 20  
定義案例類別的問題之一,在於你必須實際定義出暴露出成員資訊的類別,有時這並不是你想要的,或者你沒辦法定義出這樣的類別。舉個例子來說,字串就是一個例子,如果你有一些學生資料,每筆是 "B123456,Justin,Kaohsiung" 的格式,你希望分別取得 學號、名稱與出生地資訊,基本的作法是:
  1. def separate(s: String) = {  
  2.     val parts = s.split(",")  
  3.     if(parts.length == 3) (parts(0), parts(1), parts(2)) else None  
  4. }  
  5.   
  6. val (number, name, addr) = separate("B123456,Justin,Kaohsiung")  
  7. println(number)   // B123456  
  8. println(name)     // Justin  
  9. println(addr)     // Kaohsiung  
separate() 函式傳回三個元素的 Tuple,你運用了 Tuple 模式比對 的特性傳回分割後的個別字串。然而,如果你可以這麼作的話,程式看起來會更清楚:
  1. // 這有可能嗎?  
  2. val Student(number, name, addr) = "B123456,Justin,Kaohsiung"  
這看起來像是上面 案例類別 的模式比對,問題是字串根本不是案例類別,怎麼可能這麼作?事實上是可行的,你可以定義一個單例物件如下,就可以執行這樣的模式比對功能:
  1. object Student {  
  2.     def unapply(str: String): Option[(String, String, String)] = {  
  3.         val parts = str.split(",")  
  4.         if (parts.length == 3) Some(parts(0), parts(1), parts(2)) else None  
  5.     }  
  6. }  
  7.   
  8. val Student(number, name, addr) = "B123456,Justin,Kaohsiung"  
  9. println(number)   // B123456  
  10. println(name)     // Justin  
  11. println(addr)     // Kaohsiung  
unapply() 方法可以接受你所提供的物件(在這邊是以字串為例,事實上可以是任何類型),經用你所定義的 unapply() 方法內容處理後傳回 Option 物件,事實上,在上例的例子中,編譯器會作如下的處理:
  1. val Some((number, name, addr)) = Student.unapply("B123456,Justin,Kaohsiung")  
unapply() 方法稱之為 提取方法Extraction method),而像 Student 這樣只具備提取方法的物件稱之為 提取器Extractor),提取器讓你對非案例類別的實例,也可以進行模式比對,例如搭配 match 運算式的一個例子如下:
  1. val students = List(  
  2.                    "B123456,Justin,Kaohsiung",  
  3.                    "B98765,Monica,Kaohsiung",  
  4.                    "B246819,Bush,Taipei"  
  5.                )  
  6.   
  7. students.foreach(_ match {  
  8.     case Student(nb, name, addr) => println(nb + ", " + name + ", " + addr)  
  9. })  
也可以進一步使用模式比對的各種特性,例如使用 模式防護Pattern guard),找出住在高雄的學生姓名:
  1. val students = List(  
  2.                    "B123456,Justin,Kaohsiung",  
  3.                    "B98765,Monica,Kaohsiung",  
  4.                    "B246819,Bush,Taipei"  
  5.                )  
  6.                  
  7. students.foreach(_ match {  
  8.     case Student(_, name, addr) if addr == "Kaohsiung" => println(name)  
  9.     case _ =>  
  10. })  
相對於 unapply() 方法,apply() 方法則稱之為 注入方法Injection method),提取方法 與 注入方法 通常同時存在(但非必要),apply() 方法與 unapply() 方法的作用通常是相反的,例如:
- ApplyUnApply.scala
  1. object Student {  
  2.     def apply(number: String, name: String, addr: String) = {  
  3.         number + "," + name + "," + addr  
  4.     }  
  5.       
  6.     def unapply(str: String) = {  
  7.         val parts = str.split(",")  
  8.         if (parts.length == 3) Some(parts(0), parts(1), parts(2)) else None  
  9.     }  
  10. }  
一個使用範例如下:
scala> :load ApplyUnApply.scala
Loading ApplyUnApply.scala...
defined object Student


scala> val Student(nb, name, addr) = "A123456,John,Taipei" // 提取方法 unapply()
nb: String = A123456
name: String = John
addr: String = Taipei


scala> printf("NB=%s; Name=%s; Addr=%s\n", nb, name, addr)
NB=A123456; Name=John; Addr=Taipei

scala> val data = Student(nb, name, addr) // 注入方法 apply()
data: String = A123456,John,Taipei


沒有留言:

張貼留言

網誌存檔

關於我自己

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