模式匹配(Pattern Matching)允许我们根据标识符值的不同进行不同的运算,它通常被拿来跟C#中的if…else或switch语法结构相比较,结论往往是模式匹配比后者要更为灵活、强大。那先来分析一下它灵活、强大在哪儿。
为什么说模式匹配是灵活、强大的?
在我前面写过的几篇随笔里面,有几次提到了模式匹配,比如它能够对简单值(整数、字符串)匹配,也可以对.NET类型进行匹配,看下面两个简单的例子:
F# Code - 对简单值和.NET类型进行匹配
// 对简单值进行匹配。
let rec fibonacci x =
match x with
| x when x <= 0 -> failwith "x必须是正整数。"
| 1 -> 1
| 2 -> 1
| x -> fibonacci(x - 1) + fibonacci(x - 2)
printfn "%i" (fibonacci 2) // -> 1
printfn "%i" (fibonacci 4) // -> 3
// 对.NET类型进行匹配。
open System
let typeToString x =
match box x with
| :? Int32 -> "Int32"
| :? Double -> "Double"
| :? String -> "String"
| _ -> "Other Type"
可以看到,这里所用的模式匹配没有给人太多惊喜,不用费多大力气就可以将其转换为if…else或switch结构了。
先别急着离开,列表是FP中的典型数据结构,我们对它应用一下模式匹配看看。
F# Code - 对列表应用模式匹配
// 对列表应用模式匹配。
let listOfList = [[2; 3; 5]; [7; 11; 13]; [17; 19; 23; 29]]
let rec concatenateList list =
match list with
| head :: tail -> head @ (concatenateList tail)
| [] -> []
let rec concatenateList2 list =
if List.nonempty list then
let head = List.hd list in
let tail = List.tl list in
head @ (concatenateList2 tail)
else
[]
let primes = concatenateList listOfList
print_any primes // [2; 3; 5; 7; 11; 13; 17; 19; 23; 29]
listOfList是一个列表的列表,两个函数concatenateList和concatenateList2的功能都是将listOfList的元素连接为一个大的列表,只不过一个用模式匹配方式实现,一个使用if…then…else结构实现。可以看到concatenateList的代码更为简洁,但仅仅如此吗?在concatenateList2中,我们按照传统的看待链表(F#中的列表以链表实现)的方式,将其中的节点一个一个取出来进行处理,这种处理方式是较为具体和细节的;而在concatenateList中我们通过两个简单的模式“head :: tail”和“[]”就覆盖了列表的所有可能,可以说,我们找到了更好地分解列表这种数据结构的方式,从而可以更为通用地处理列表。
类似的,再来看看Union类型的情况。Union类型,有时称为sum类型或discriminated union,可将一组具有不同含义或结构的数据组合在一起。它的一个典型应用是表示一颗树:
F# Code - 对Union类型应用模式匹配
type BinaryTree<'a> =
| Leaf of 'a
| Node of BinaryTree<'a> * BinaryTree<'a>
let rec printBinaryTreeValues t =
match t with
| Leaf x -> printfn "%i" x
| Node (l, r) ->
printBinaryTreeValues l
printBinaryTreeValues r
printBinaryTreeValues (Node ((Node (Leaf 1, Leaf 2)), (Node (Leaf 3, Leaf 4))))
这里通过BinaryTree<'a>定义一个泛型二叉树类型,printBinaryTreeValues函数用于打印其节点的值,这里需要判断节点的类型(子树还是叶子),有趣的是,Leaf和Node自动抽象为“模式”,不需要任何额外的工作。这样就可以看到一些所谓“灵活、强大”的影子了,对于Union类型所表示的数据结构,模式匹配可以极为简单、自然地分解、处理它。
除了列表和Union类型,元组对于模式匹配的“自适应”也是类似的,这些已经够我们解决很多问题了。那对于其它的更复杂的场景或者更特殊的领域,F#还有什么大招呢?你一定能想得到,这就是活动模式。