模式匹配在 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"
来源: https://www.cnblogs.com/xiandnc/p/9388259.html