【问题标题】:F# multi-condition if/else versus matchingF# 多条件 if/else 与匹配
【发布时间】:2015-08-08 01:28:52
【问题描述】:

我是 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#/Functional 方式是什么?还是我应该坚持 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 

【问题讨论】:

    标签: f# functional-programming


    【解决方案1】:

    您的条件都处理不同的事情,彼此无关,所以ifs 的字符串就可以了。我唯一推荐的是使用elif 而不是else if

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

    ifs 也是如此。例如,这不会编译:

    let x = if a < 5 then 7
    

    为什么?因为编译器知道a &lt; 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,所以整个thing 将返回 unit 并且不打印任何内容。我不能说这是否会发生在您的特定情况下,因为我没有看到整个函数定义。

    【讨论】:

      【解决方案2】:

      有时,正如 Fyodor Soikin 正确解释的那样,一系列 ifelse ifelse 表达式是最好的选择,尽管我会使用 elif 而不是 else if

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

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

      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) &lt; eps,即使fc = 0. 计算结果为true

      不过,评估像 ((b - a) * 0.5) &lt; eps 这样的简单表达式会非常快,以至于您可能无法测量它,因此如果您认为这种算法表达方式更具可读性,您可以决定权衡一下效率低下,可读性更好。

      不过,在这种情况下,我不认为它更具可读性,所以我仍然会使用一系列 ifelifelse 表达式。

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

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

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


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

      case (fc == 0.0, ((b - a) * 0.5) < eps) of
        (True, _) -> "fc is 0"
        (_, True) -> "((b - a) * 0.5) is less than eps"
        _ -> "etc."
      

      元组中的第二个表达式只会在必要时进行评估,因此如果第一个案例 ((True, _)) 匹配,则无需评估第二个表达式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        • 2011-12-20
        • 1970-01-01
        • 1970-01-01
        • 2021-10-01
        相关资源
        最近更新 更多