1. 程式人生 > >函數式編程之-模式匹配(Pattern matching)

函數式編程之-模式匹配(Pattern matching)

arp 遞歸 rec 順序 lis box default googl record

模式匹配在F#是非常普遍的,用來對某個值進行分支匹配或流程控制。

模式匹配的基本用法

模式匹配通過match...with表達式來完成,一個完整的模式表達式長下面的樣子:

match [something] with 
| pattern1 -> expression1
| pattern2 -> expression2
| pattern3 -> expression3

當你第一次使用模式匹配,你可以認為他就是命令式語言中的switch...case或者說是if...else if...else。只不過模式匹配的能力要比switch...case強大的多。
考慮下面的例子:

let x = 
    match 1 with 
    | 1 -> "a"
    | 2 -> "b"  
    | _ -> "z" 

顯然,x此時的值是"a",因為第一個匹配分支就匹配正確了。在這個表達式裏第三個匹配分支有點特殊:

| _ -> "z"

通配符_在這裏起到了default的作用,上面的所有分支如果都匹配失敗,則最終會匹配的這個分支。
1.分支是有順序的
但是這三個分支的順序是可以隨便改的,也就意味著我們可以把通配符分支放到第一個位置:

 let x = 
    match 1 with 
    | _ -> "z" 
    | 1 -> "a"
    | 2 -> "b" 

在這個例子中,第一個匹配分支會勝出,同時編譯器也會給出一個警告:其他的分支從來都不會被用到。
這說明在模式匹配中,分支的順序是非常重要的,應該把更加具體的匹配分支放在前面,包含通配符的分支應該放在最後面。
2.模式匹配是一個表達式
模式匹配是一個表達式,所有的分支都應該返回同樣的類型,考慮下面的例子:

let x = 
    match 1 with 
    | 1 -> 42
    | 2 -> true  // error wrong type
    | _ -> "hello" // error wrong type

不同的分支應該返回想通類型的值。

3.至少有一個分支能被匹配到
考慮下面的例子:

let x = 
    match 42 with 
    | 1 -> "a"
    | 2 -> "b"

由於兩個分支都沒有匹配到,編譯器將會給出警告,你至少要寫一個能夠匹配到的分支,例如為其添加通配符分支。
你可以通過添加通配符分支讓編譯器不在發出警告,但是在實際實踐中,你應該盡可能的添加可能存在的分支,例如你在對一個選擇類型做模式匹配:

type Choices = A | B | C
let x = 
    match A with 
    | A -> "a"
    | B -> "b"
    | C -> "c"

如果後來某一天你在Choices類型裏添加了一個新的選項D,編譯器就會對之前的對Choices的模式匹配發出警告,提示你添加新的分支。試想如果你之前加了通配符,編譯器就會吞掉這個警告,進而產生bug。

匹配元組(Tuple)

模式匹配幾乎可以匹配F#所有的類型,例如元組:

let y = 
    match (1,0) with 
    | (1,x) -> printfn "x=%A" x
    | (_,x) -> printfn "other x=%A" x

顯然第一個分支會被匹配到。
你可以把多個模式寫在同一個分支上,當多個模式是的關系時用|隔開:

type Choices = A | B | C | D
let x = 
    match A with 
    | A | B | C -> "a or b or c"
    | D -> "d"

當多個模式是的關系時用&隔開:

let y = 
    match (1,0) with 
    | (2,x) & (_,1) -> printfn "x=%A" x 

匹配list

匹配list只有三種模式:

  • [x;y;z]用來顯示匹配list中的元素
  • head::tail head會匹配到第一個元素,其他的元素會匹配到tail,這個模式常用來對list做遞歸
  • [] 會匹配到空的list
let rec loopAndPrint aList = 
    match aList with 
    | [] -> 
        printfn "empty" 
    | x::xs -> 
        printfn "element=%A," x
        loopAndPrint xs 

loopAndPrint [1..5]

當[]模式被匹配到,說明list已經為空,可以作為遞歸的終止條件;
x::xs模式會將第一個元素匹配到x中,剩余的元素被匹配到xs,然後xs又被當做參數做下一次遞歸

匹配Recoard type和Descriminated Union type...

//record type
type Person = {First:string; Last:string}
let person = {First="john"; Last="doe"}
match person with 
| {First="john"}  -> printfn "Matched John" 
| _  -> printfn "Not John" 

//union type
type IntOrBool= I of int | B of bool
let intOrBool = I 42
match intOrBool with 
| I i  -> printfn "Int=%i" i
| B b  -> printfn "Bool=%b" b

其他

1.as關鍵字
你可以把模式用as關鍵字指向另一個名稱:

let y = 
    match (1,0) with 
    | (x,y) as t -> 
        printfn "x=%A and y=%A" x y
        printfn "The whole tuple is %A" t

2.匹配子類
:?用來匹配類型,例如第一個分支用來匹配int類型:

let detectType v =
    match box v with
        | :? int -> printfn "this is an int"
        | _ -> printfn "something else"

匹配類型並不是一種好的實踐,正如你在OO語言裏編寫if type ==...一樣。
when條件
有時候你需要對匹配完成的值做一些條件判斷:

let elementsAreEqual aTuple = 
    match aTuple with 
    | (x,y) -> 
        if (x=y) then printfn "both parts are the same" 
        else printfn "both parts are different"

這種情況可以通過在模式中添加when條件來做到:

let elementsAreEqual aTuple = 
    match aTuple with 
    | (x,y) when x=y -> 
        printfn "both parts are the same" 
    | _ ->
        printfn "both parts are different" 

Active pattern

when語句盡管可以給模式添加一些條件,但是當語句過於復雜的時候可以考慮某個分支的模式定義為一個方法:

open System.Text.RegularExpressions

// create an active pattern to match an email address
let (|EmailAddress|_|) input =
   let m = Regex.Match(input,@".+@.+") 
   if (m.Success) then Some input else None  

// use the active pattern in the match  
let classifyString aString = 
    match aString with 
    | EmailAddress x -> 
        printfn "%s is an email" x
        
    // otherwise leave alone
    | _ -> 
        printfn "%s is something else" aString

//test
classifyString "[email protected]"
classifyString "google.com"

函數式編程之-模式匹配(Pattern matching)