F#多条件if / else与匹配

时间:2015-08-07 12:24:02

标签: f# functional-programming

我是F#的新手,并且一直在实施简单的算法来学习语言结构。我使用if/else实现了二分法,然后想要学习如何使用匹配。

if fc = 0.0                   then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if ((b - a) * 0.5) < eps then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if new_count = n         then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if fc * fa < 0.0         then bisect a c new_count
else if fc * fb < 0.0         then bisect c b new_count 

我发现使用match a, b, fa, fb, fc会导致类型错误,如果我只使用一个参数,我基本上可以忽略参数并检查我的条件。什么是惯用的F#/功能方式来使用匹配?或者我应该坚持if / else?

 match a with 
    | a when fc = 0.0              ->  printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
    | a when ((b - a) * 0.5) < eps -> printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
    | a when new_count = n         -> printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
    | a when fc * fa < 0.0         -> bisect a c new_count
    | a when fc * fb < 0.0         -> bisect c b new_count 

2 个答案:

答案 0 :(得分:7)

你的条件都处理不同的事情,彼此无关,所以if的字符串就好了。我建议的唯一方法是使用elif代替else if

match应该被理解为“鉴于这种东西可以是不同的味道,这里是如何处理这些味道”。 match的一个特殊优势是编译器会弄清楚,并告诉你,你是否错过了任何“风味”。特别是,您在问题中提供的代码应该产生编译器警告,抱怨“不完整模式匹配此表达式”。想一想:当没有一个案例匹配时,那个表达式的结果会是什么?

对于if s,情况也是如此。例如,这不编译:

let x = if a < 5 then 7

为什么呢?因为编译器在a < 5(即它应该是7)时知道结果应该是什么,但是它应该是什么呢?编译器无法为您决定,因此会产生错误 另一方面,这将编译:

let x = if a < 5 then 7 else 8

但是在你的特定情况下,编译器可以让你逃避这一点,因为你的所有分支都返回unit(为什么?因为printf返回unit,而所有其他分支都是递归的)。换句话说,以下将编译:

let x = if a < 5 then ()

以下内容:

let x = if a < 5 then printf "boo!"

编译器可以让你逃避这一点,因为unit很特殊:它只能有一个值(即()),因此编译器可以决定对于你,当条件不是true时,表达式的结果是什么。

这样做的一个实际结果就是,如果你没有仔细考虑你的情况非常谨慎,那么可能会发生这样的事情,因此你的条件都不是true,所以整件事将返回unit并且不会打印任何内容。我不能说在你的特定情况下是否会发生这种情况,因为我没有看到整个函数的定义。

答案 1 :(得分:4)

有时候,正如Fyodor Soikin正确解释的那样,尽管我使用if代替else ifelseelifelse if表达式是最佳选择。 {1}}。

有时有意义的是先计算一些值,然后将它们放入一个可以匹配的数据结构中 - 通常是一个元组。

使用上述问题的简化版本,假设您只需检查前两种情况,可以这样做:

match fc = 0., ((b - a) * 0.5) < eps with
| true, _ -> "fc is 0"
| _, true -> "((b - a) * 0.5) is less than eps"
| _ -> "etc."

注意fc = 0.之后的逗号,它将匹配表达式转换为元组 - 更具体地说是bool * bool

这样做的缺点是效率低下,因为即使((b - a) * 0.5) < eps评估为{,总是正在评估表达式fc = 0. {1}}。

仍然,评估像true这样的简单表达式会非常快,以至于你可能无法测量它,所以如果你认为这种表达算法的方式更具可读性,你可以决定交易关闭这种低效率以提高可读性。

在这种情况下,我不认为它更具可读性,所以我仍然会使用一系列((b - a) * 0.5) < epsifelif表达式。

这是一个预先计算值并将它们放入元组更有意义的示例:

else

这是FizzBu​​zz kata的常见实现。这有意义,因为第一次匹配需要两个模数,所以没有低效率,代码也很可读。

关于效率低下的观点对于F#来说是正确的,因为热切地评估了F#。另一方面,在Haskell中,表达式被懒惰地评估,因此您可以在不损失效率的情况下执行以下操作:

match number % 3, number % 5 with
| 0, 0 -> "FizzBuzz"
| _, 0 -> "Buzz"
| 0, _ -> "Fizz"
| _    -> number.ToString()

元组中的第二个表达式只会在必要时进行评估,因此如果第一个案例(case (fc == 0.0, ((b - a) * 0.5) < eps) of (True, _) -> "fc is 0" (_, True) -> "((b - a) * 0.5) is less than eps" _ -> "etc." )匹配,则无需评估第二个表达式。

相关问题