在ml家族语言中,人们倾向于选择模式匹配而不是if/else
结构。在f#中,在许多情况下,在模式匹配中使用守卫可以很容易地取代if/else
。
例如,一个简单的delete1
函数可以重写而不使用if/else
(参见delete2
):
let rec delete1 (a, xs) =
match xs with
| [] -> []
| x::xs' -> if x = a then xs' else x::delete1(a, xs')
let rec delete2 (a, xs) =
match xs with
| [] -> []
| x::xs' when x = a -> xs'
| x::xs' -> x::delete2(a, xs')
另一个例子是求解二次函数:
type Solution =
| NoRoot
| OneRoot of float
| TwoRoots of float * float
let solve1 (a,b,c) =
let delta = b*b-4.0*a*c
if delta < 0.0 || a = 0.0 then NoRoot
elif delta = 0.0 then OneRoot (-b/(2.0*a))
else
TwoRoots ((-b + sqrt(delta))/(2.0*a), (-b - sqrt(delta))/(2.0*a))
let solve2 (a,b,c) =
match a, b*b-4.0*a*c with
| 0.0, _ -> NoRoot
| _, delta when delta < 0.0 -> NoRoot
| _, 0.0 -> OneRoot (-b/(2.0*a))
| _, delta -> TwoRoots((-b + sqrt(delta))/(2.0*a),(-b - sqrt(delta))/(2.0*a))
我们应该使用模式匹配和保护来忽略丑陋的if/else
结构吗?
使用模式匹配与守卫是否有任何性能影响?我的印象是,它似乎很慢,因为在运行时已经检查了模式匹配。
正确的答案可能是,这取决于,但我猜测,在大多数情况下,编译后的表示是相同的。例如
let f b =
match b with
| true -> 1
| false -> 0
和
let f b =
if b then 1
else 0
都可以翻译成
public static int f(bool b)
{
if (!b)
{
return 0;
}
return 1;
}
考虑到这一点,这主要是风格问题。就我个人而言,我更喜欢模式匹配,因为用例总是对齐的,使其更具可读性。此外,它们(可以说)更容易在以后扩展以处理更多的情况。我认为模式匹配是if
/then
/else
的演变。
模式匹配也没有额外的运行时成本,无论是否使用保护符
都有自己的位置。人们更习惯使用If/else结构来检查值,而模式匹配就像类固醇上的If/else。模式匹配允许您对数据的decomposed
结构进行比较,同时使用守卫来指定分解数据的部分或其他值的一些附加条件(特别是在递归数据结构或f#中所谓的区分联合的情况下)。
我个人更喜欢使用if/else来进行简单的值比较(true/false, int等),但是如果你有一个递归的数据结构,或者你需要与它的分解值进行比较,那么没有什么比模式匹配更好的了。
首先让它工作,使它优雅和简单,然后如果你发现一些性能问题,然后检查性能问题(这主要是由于一些其他的逻辑,而不是由于模式匹配)
同意@Daniel的观点,模式匹配通常更灵活。检查这个实现:
type Solution = | Identity | Roots of float list
let quadraticEquation x =
let rec removeZeros list =
match list with
| 0.0::rest -> removeZeros rest
| _ -> list
let x = removeZeros x
match x with
| [] -> Identity // zero constant
| [_] -> Roots [] // non-zero constant
| [a;b] -> Roots [ -b/a ] // linear equation
| [a;b;c] ->
let delta = b*b - 4.0*a*c
match delta with
| delta when delta < 0.0 ->
Roots [] // no real roots
| _ ->
let d = sqrt delta
let x1 = (-b-d) / (2.0*a)
let x2 = (-b+d) / (2.0*a)
Roots [x1; x2]
| _ -> failwithf "equation is bigger than quadratic: %A" x
还要注意在https://fsharpforfunandprofit.com/learning-fsharp/中不鼓励使用if-else。
我在自己编写的素数生成器上做了一些测试,据我所知,"if then else"明显比模式匹配慢,虽然不能解释为什么,但就我所测试的而言,f#的命令式部分在优化算法时比递归函数风格的运行时间慢。