模式匹配第二彈:元組,range 和類型

Leslie_88 8年前發布 | 8K 次閱讀 range Swift Apple Swift開發

上一篇文章中,我們已經看過了使用 switch 來對枚舉進行基本的模式匹配。那如果對除枚舉外的其它類型使用 switch來進行模式匹配會怎樣呢?

對元組進行模式匹配

在 Swift 當中,switch 并不像 ObjC 一樣只能對整型或枚舉進行匹配。

事實上,我們可以使用 switch 對很多類型進行匹配,包括(但不僅限于)元組。

這意味著我們只要將多個數據組合在一個元組中,就可以一次性匹配多個數據。比如說,如果你有一個 CGPoint,并且你想確定這個點是否位于某個坐標軸上,則可以使用 switch 來匹配它的 .x 和 .y 屬性!

let point = CGPoint(x: 7, y: 0)
switch (point.x, point.y) {
  case (0,0): print("On the origin!")
  case (0,_): print("x=0: on Y-axis!")
  case (_,0): print("y=0: on X-axis!")
  case (let x, let y) where x == y: print("On y=x")
  default: print("Quite a random point here.")
}

注意我們在上一篇文章中使用過的 _ 通配符,以及第四個 case 使用到的 (let x, let y) 可以對變量進行綁定,然后使用 where 來檢查它們是否相等。

Case 是按順序判斷的

還有一點要注意的是,switch 是按 case 模式被指定的順序來判斷求值的,并且它會在匹配到第一個滿足的 case 后跳出。與 C 和 Objective-C 不同,我們不需要使用 break 關鍵字1

這意味著在上面的代碼中,如果坐標是 (0, 0),則它會匹配第一個 case 打印出 "On the origin!",并就此打住,就算(0, _) 和 (_, 0) 也符合匹配的條件,它也不會再去進行匹配。因為它已經在第一個匹配之后跳出了。

字符串與字符

為什么要止步于元組呢?在 Swift 當中,我們也可以使用 switch 來對很多原生類型進行匹配,包括字符串和字符,比如:

let car: Character = "J"
switch car {
  case "A", "E", "I", "O", "U", "Y": print("Vowel")
  default: print("Consonant")
}

可以注意到,我們可以使用按逗號分隔的多個模式來進行匹配,使符合這些模式的匹配(這里是匹配所有的元音字母)都執行同一段代碼。這可以避免我們寫很多重復的代碼。

Range

Range 在模式匹配中也很有用。提醒一下,Range<T> 是一個泛型類型,它包含了 T 類型的 start 和 end 成員,同時T 必須是一個 ForwardIndexType。這包括 Int 和 Character 在內的許多類型。

我們可以使用 Range(start: 1900, end: 2000) 來顯式地聲明一個 range,也可以使用語法糖操作符 ..<(不包含最后一個數 end)或 ...(包含最后一個數 end),所以我們也可以將上面的 range 寫為 1900..<2000(更方便也更易讀)

那么我們如何在 switch 當中使用它們呢?其實相當簡單,在 case 模式中使用 range 來判斷值是否落于這個范圍內!

let count = 7
switch count {
  case Int.min..<0: print("Negative count, really?")
  case 0: print("Nothing")
  case 1: print("One")
  case 2..<5: print("A few")
  case 5..<10: print("Some")
  default: print("Many")
}

可以看到我們在 case 當中混用了 Int 整型值與 Range<Int> 的值。這樣的使用并沒有任何問題,只要我們保證覆蓋了所有可能的情況。

雖說 Int 是最常用的 range 類型,我們也可以使用其它的 ForwardIndexType 類型,包括… Character!還記得上面寫的代碼么?它有一點問題,那就是對于標點符號以及其它不是 A-Z 的字符,它也會打印出 “Consonant”。讓我們來解決這個問題2(同時也增加了小寫字母):

func charType(car: Character) -> String {
  switch car {
    case "A", "E", "I", "O", "U", "Y", "a", "e", "i", "o", "u", "y":
      return "Vowel"
    case "A"..."Z", "a"..."z":
      return "Consonant"
    default:
      return "Other"
  }
}
print("Jules Verne".characters.map(charType))
// ["Consonant", "Vowel", "Consonant", "Vowel", "Consonant", "Other", "Consonant", "Vowel", "Consonant", "Consonant", "Vowel"]

類型

至此一切順利,但我們能不能更進一步呢?答案是當然沒問題:讓我們把模式匹配用在… 類型上!

在這里,我們定義了三個結構體,并遵守相同的協議:

protocol Medium {
  var title: String { get }
}
struct Book: Medium {
  let title: String
  let author: String
  let year: Int
}
struct Movie: Medium {
  let title: String
  let director: String
  let year: Int
}
struct WebSite: Medium {
  let url: NSURL
  let title: String
}

// And an array of Media to switch onto
let media: [Medium] = [
  Book(title: "20,000 leagues under the sea", author: "Jules Vernes", year: 1870),
  Movie(title: "20,000 leagues under the sea", director: "Richard Fleischer", year: 1955)
]

然后我們要如何對 Medium 使用 switch 的模式匹配,讓它對 Book 和 Movie 做不同的事呢?簡單,在模式匹配中使用as 和 is

for medium in media {
  // The title part of the protocol, so no need for a switch there
  print(medium.title)
  // But for the other properties, it depends on the type
  switch medium {
  case let b as Book:
    print("Book published in \(b.year)")
  case let m as Movie:
    print("Movie released in \(m.year)")
  case is WebSite:
    print("A WebSite with no date")
  default:
    print("No year info for \(medium)")
  }
}

注意到對 Book 和 Movie 使用的 as,我們需要確定它們是不是特定的類型,如果是,則將它們轉換后的類型賦值給一個常量(let b 或 let m),因為我們之后要使用到這個常量3

而另一方面,對 WebSite 我們只使用了 is,因為我們只需要檢查 medium 是不是一個 Website 類型,如果是,我們并沒有對它進行轉換與存儲在常量中(我們不需要在 print 語句中使用到它)。這與使用 case let _as Website 有點類似,因為我們只關心它是不是 Website 類型,而不需要它的對象的值。

注意:如果必須在 swtich 匹配中使用到 as 和 is,這里有可能存在代碼異味,比如,在上面這個特定的例子中,在protocol Medium 當中添加一個 releaseInfo: String { get } 屬性就比使用 switch 來對不同的類型進行匹配要好。

下一步

在接下來的部分,我們會學習如何創建可以直接使用于模式匹配的自定義類型,探索更多的語法糖,并看到如何在 switch語句之外使用模式匹配,以及更加復雜的匹配表達式… 迫不及待了吧!

  1. 可以使用 fallthrough 關鍵字來讓求值判斷流向下一個 case。但是在實踐上要使用到這個關鍵字的場景很少,并不經常會碰到。
  2. 當然,這種字符串的分析方法并不是最好的,也不是值得推薦的 —— 因為 Unicode 字符以及本地化都比這復雜得多。所以類似這樣的功能我們更應該使用 NSCharacterSet,考慮當前的 NSLocale 把哪些字母定義為元音(“y” 是元音嗎?還有 “?” or “?” 呢?),等等。不要把這個例子看得太認真,我只是用它來展示 switch + Range 的強大而已。
  3. 盡管與 if let b = medium as? Book 表達式很相似 —— 當 medium 可以被轉換為特定類型的時候,它們都將其綁定到一個變量上 —— 但是在模式匹配中我們要使用 as 而非 as?。盡管它們的機制很相似,但是它們的語義是不同的(“嘗試進行類型轉換,如果失敗就返回 nil” vs “判斷這個模式是不是匹配這種類型”)。

來源:http://swift.gg/2016/04/27/pattern-matching-2/ 

 本文由用戶 Leslie_88 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!