F#-PatternMatching
偶然看到一篇不错的F#入门文章,现在学习的同时翻译一下,讲的比较基础,但是学习正是一个循序渐进的过程。
原文地址:
http://diditwith.net/2008/02/19/WhyILoveFPatternMatching.aspx
pattern matching是我们着迷F#的另一个原因,本质上,pattern match接受一个输入,还有着一组规则。这组规则将对输入进行匹配,如果匹配成功则返回结果。
先看看我们熟悉的斐波那契函数:
#light let rec fib n = match n with | 0 -> 0 | 1 -> 1 | _ -> fib(n - 1) + fib(n - 2)
上面的代码简单清晰,相信都读得懂。事实上这个简单的match...with代码块完全等同于下面的C#代码:
static int Fib(int n) { switch (n) { case 0: return 0; case 1: return 1; default: return Fib(n - 1) + Fib(n - 2); } }
如果单纯是这样的话,那么这个match pattern显然没什么特别之处,幸运的是,F#中的match pattern和传统的switch模式有着巨大的差异。
第一个区别是微妙但是深远的,match pattern有返回值,它非常类似于一个接受一个参数并返回一个结果的函数,考虑按照下面的方式对代码进行改写:
#light
let rec fib n =
let result = match n with
| 0 -> 0
| 1 -> 1
| _ -> fib(n - 1) + fib(n - 2)
result
上面的例子也许有些不自然,但是它很好的表明了这个特性。如果要用C#模拟这段代码则是相当丑陋的。
static int Fib(int n)
{
int result;
switch (n)
{
case 0:
result = 0;
break;
case 1:
result = 1;
break;
default:
result = Fib(n - 1) + Fib(n - 2);
break;
}
return result;
}
因为switch语句没有返回值,所以我们必须声明一个状态变量,然后把它和break语句洒满所有的case。我们可以看到从本质上match pattern更像一个函数而switch语句更像一个巨大的goto语句代码块。
除此之外,match pattern还有着一系列特性使得它不同于传统的switch。它还可以:
1 包含检测规则。(仅仅当输入值x满足某个条件时才进行匹配)。
2 绑定值到名称
3 分解类型结构
现在就让我们依次来看看每一个特性:
首先考虑这样一个带有检测条件的斐波那契额函数:
#light
let rec fib n =
match n with
| _ when n < 0 -> failwith "value cannot be less than 0."
| 0 -> 0
| 1 -> 1
| _ -> fib(n - 1) + fib(n - 2)
现在就更有点意思了,在C#里面,我们必须加入一条if语句来检测输入参数,但是在match pattern里面它直接变成了匹配规则的一部分。
现在让我们看看将值绑定到名称。
到目前为止,我们都一直使用着match...with语句,现在我们来看看一种有些不是那么必要的非正统写法,它可以展示利用规则把值绑定到名称上是多么容易。
这种非正统的语法适用于一个函数直接接受一个参数并且返回这个参数的match语句结果的情况。这种语法中不再指明参数,而是用function关键字替代,因为match...with语句需要指明参数,所以match...with语句也不存在了,于是最终我们只剩下一个function和一组匹配规则。而因为参数是不指明的,所以我们需要在匹配规则中把参数值绑定到名称上。
几行代码胜过千言万语:
let rec fib = function
| x when x < 0 -> failwith "value cannot be less than 0."
| 0 | 1 as x -> x
| x -> fib(x - 1) + fib(x - 2)
在上面的例子中因为没有指明输入参数,所以我们在匹配规则中把它绑定到了名称"x"上,并且我们把0和1的匹配合并让它们成为一种并集(或的关系)。同样请注意,在上面我们展示了两种绑定方法,一种是在要匹配模式中直接简单的写上名称,还有一种是使用as关键字。
在最后一种match pattern特性中我们将看到它对结构的拆解。
最近,我们发现当不指定out参数的时候,F#会自动将Dictionary<TKey, TValue>.TryGetValue的结果组合为一个 tuple(这里是2元组)。我们看看它如何精彩的拆解这个元组结构:
#light
open System.Collections.Generic
let getValueOrDefault (dict : #IDictionary<'a,'b>) key defaultValue =
match dict.TryGetValue key with
| true, value -> value
| _ -> defaultValue
上面的例子不仅仅拆解了结果的2元组,而且把解析出的第二个值绑定到了value上面,Sweet!