函数式编程之-模式匹配(Pattern matching)

时间:2023-03-09 17:50:44
函数式编程之-模式匹配(Pattern matching)

模式匹配在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 "alice@example.com"
classifyString "google.com"