【问题标题】:Pattern matching with guards vs if/else construct in F#在 F# 中使用守卫与 if/else 构造进行模式匹配
【发布时间】:2011-12-20 15:51:17
【问题描述】:

在 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 构造吗?

将模式匹配与警卫一起使用是否会对性能产生影响?我的印象是它似乎很慢,因为在运行时检查了模式匹配。

【问题讨论】:

  • 我很确定 if/else 语句也必须在运行时进行评估...

标签: f# functional-programming pattern-matching guard-clause


【解决方案1】:

正确的答案可能是视情况而定,但我推测,在大多数情况下,编译后的表示是相同的。举个例子

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的演变。

模式匹配也没有额外的运行时成本,无论有无保护。

【讨论】:

  • 如果模式中有“何时”怎么办?编译后的表示会改变吗?
  • 它只是添加了嵌套的ifs。一切都还在编译。在几乎所有情况下,模式匹配和等效的 if/then/else 都会编译为相同的代码。
  • 实际上,根据我的经验,复杂的模式匹配编译为比复杂的 if/then/else 语句更高效的代码,因为编译器在为您删除冗余条件评估方面做得不错。
  • 无论哪种方式,我都认为代码是好的,但你走了:使用模式匹配的另一个原因。
【解决方案2】:

两者都有自己的位置。人们更习惯于使用 If/else 构造来检查一个值,因为模式匹配就像类固醇上的 If/else。模式匹配允许您与数据的decomposed 结构进行比较,并使用 gaurds 来指定分解数据部分或其他值的一些附加条件(特别是在递归数据结构或所谓的区分联合的情况下)在 F# 中)。

我个人更喜欢使用 if/else 进行简单的值比较(真/假、整数等),但如果您有递归数据结构或需要与分解后的值进行比较的东西,没有什么比这更好的了模式匹配。

首先让它工作,让它优雅和简单,然后如果你看起来有一些性能问题,然后检查性能问题(这主要是由于一些其他逻辑而不是由于模式匹配)

【讨论】:

  • +1 表示“分解”部分。区分模式匹配和 if/else 构造实际上是一个很好的点。
【解决方案3】:

同意@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。它被认为是功能性较差的出价。

【讨论】:

    【解决方案4】:

    我在一个自写的素数生成器上做了一些测试,据我所知,“if then else”比模式匹配慢得多,虽然无法解释为什么,但据我所知测试了 F# 的命令部分在优化算法方面的运行时间比递归函数样式慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-28
      • 2019-03-01
      • 1970-01-01
      • 2015-08-15
      • 2021-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多