F# 中使用守卫与 if/else 结构进行模式匹配

发布于 2024-12-13 14:53:43 字数 1221 浏览 3 评论 0原文

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

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

In ML-family languages, people tend to prefer pattern matching to if/else construct. In F#, using guards within pattern matching could easily replace if/else in many cases.

For example, a simple delete1 function could be rewritten without using if/else (see 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') 

Another example is solving quadratic functions:

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))

Should we use pattern matching with guards to ignore ugly if/else construct?

Is there any performance implication against using pattern matching with guards? My impression is that it seems to be slow because pattern matching has be checked at runtime.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情感失落者 2024-12-20 14:53:43

正确的答案可能是这取决于,但我推测,在大多数情况下,编译后的表示是相同的。作为一个例子

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

无论有或没有防护,模式匹配都不会产生额外的运行时成本。

The right answer is probably it depends, but I surmise, in most cases, the compiled representation is the same. As an example

let f b =
  match b with
  | true -> 1
  | false -> 0

and

let f b =
  if b then 1
  else 0

both translate to

public static int f(bool b)
{
    if (!b)
    {
        return 0;
    }
    return 1;
}

Given that, it's mostly a matter of style. Personally I prefer pattern matching because the cases are always aligned, making it more readable. Also, they're (arguably) easier to expand later to handle more cases. I consider pattern matching an evolution of if/then/else.

There is also no additional run-time cost for pattern matching, with or without guards.

愁杀 2024-12-20 14:53:43

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

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

首先让它工作并使其优雅和简单,然后如果您发现一些性能问题,则检查性能问题(这主要是由于一些其他逻辑而不是由于模式匹配)

Both have their own place. People are more used to If/else construct for checking a value where as pattern matching is like a If/else on steroids. Pattern matching allows you to sort of compare against the decomposed structure of the data along with using gaurds for specifying some additional condition on the parts of the decomposed data or some other value (specially in case of recursive data structures or so called discriminated unions in F#).

I personally prefer to use if/else for simple values comparisons (true/false, ints etc), but in case you have a recursive data structure or something which you need to compare against its decomposed value than there is nothing better than pattern matching.

First make it work and make it elegant and simple and then if you seem some performance problem then check for performance issues (which mostly will be due to some other logic and not due to pattern matching)

笑叹一世浮沉 2024-12-20 14:53:43

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

Agree with @Daniel that pattern matching is usually more flexible.
Check this implementation:

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

Also notice in https://fsharpforfunandprofit.com/learning-fsharp/ that it is discouraged to use if-else. It is considered a bid less functional.

棒棒糖 2024-12-20 14:53:43

我在自写素数生成器上做了一些测试,据我所知,“if then else”明显比模式匹配慢,但无法解释原因,但据我测试了命令式当涉及到最佳算法时,F# 的一部分的运行时间比递归函数式风格慢。

I did some testing on a self writen prime number generator, and as far as i can say there is "if then else" is significantly slower than pattern matching, can't explain why though, but I as far as I have tested the imperativ part of F# have a slower run time than recursive functional style when it come to optimal algorithms.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文