【问题标题】:How to match multiple copies of a value?如何匹配一个值的多个副本?
【发布时间】:2013-01-28 11:46:05
【问题描述】:

F# 的模式匹配非常强大,写起来感觉很自然:

match (tuple1, tuple2) with
| ((a, a), (a, a)) -> "all values are the same"
| ((a, b), (a, b)) -> "tuples are the same"
| ((a, b), (a, c)) -> "first values are the same"
// etc

但是,第一个模式匹配会导致编译器错误:

'a' is bound twice in this pattern

有没有比以下更清洁的方法?

match (tuple1, tuple2) with
| ((a, b), (c, d)) when a = b && b = c && c = d -> "all values are the same"
| ((a, b), (c, d)) when a = c && b = d -> "tuples are the same"
| ((a, b), (c, d)) when a = c -> "first values are the same"
// etc

【问题讨论】:

  • 对于任何感兴趣的人:“F# 的模式匹配非常强大”。 F# 继承的模式匹配的 ML 风格实际上故意不那么强大。具体来说,ML 将模式限制为所谓的线性模式,以保证匹配模式所花费的时间量受模式大小的限制。在 F# 中,活动模式规避了这一点,让您可以在模式匹配的过程中做任何事情。缺点是性能难以预测。 Mathematica 将这一点发挥到了极致。

标签: f# pattern-matching


【解决方案1】:

这是 F# 的“活动模式”的完美用例。您可以像这样定义其中的几个:

let (|Same|_|) (a, b) =
    if a = b then Some a else None

let (|FstEqual|_|) ((a, _), (c, _)) =
    if a = c then Some a else None

然后清理与它们匹配的模式;注意第一种情况(所有值都相等)如何使用嵌套的Same 模式来检查元组的第一个和第二个元素是否相等:

match tuple1, tuple2 with
| Same (Same x) ->
    "all values are the same"
| Same (x, y) ->
    "tuples are the same"
| FstEqual a ->
    "first values are the same"
| _ ->
    failwith "TODO"

性能提示:我喜欢用inline 标记这些简单的活动模式——因为活动模式中的逻辑很简单(只有几条 IL 指令),因此内联它们并避免函数调用。

【讨论】:

    【解决方案2】:

    您可以使用parameterized active patterns 来解决此问题。

    let (|TuplePairPattern|_|) ((p1, p2), (p3, p4)) ((a, b), (c, d)) =
        let matched =
            [(p1, a); (p2, b); (p3, c); (p4, d)]
            |> Seq.groupBy fst
            |> Seq.map (snd >> Set.ofSeq)
            |> Seq.forall (fun s -> Set.count s = 1)
        if matched then Some () else None
    

    特别是,您应该以文字(字符、字符串等)的形式定义模式。

    match tuple1, tuple2 with
    | TuplePairPattern(('a', 'a'), ('a', 'a')) -> "all values are the same"
    | TuplePairPattern(('a', 'b'), ('a', 'b')) -> "tuples are the same"
    | TuplePairPattern(("a", "b"), ("a", "c")) -> "first values are the same"
    // etc
    

    【讨论】:

    • 为了消除一些相等性检查,引入了很多的开销。如果您只执行此match 一次或两次可能不会引起注意,但如果您在循环某些数据时(例如)使用它肯定会影响性能。
    • 同意。当可读性是关键而性能不是关键问题时,这是一种很好的方法。
    【解决方案3】:

    我认为,最优雅的方式可以通过结合@Stephen Swensen 和@pad 提供的两个出色答案来完成。

    第一个想法是结构(一个包含两个元组的元组)可以解包一次,而不是在每个match 情况下都解包。
    第二个想法是处理值序列,所有这些值必须彼此相等。

    代码如下:

    let comparer ((a,b),(c,d)) =
        let same = Set.ofSeq >> Set.count >> ((=) 1)
        if   same[a; b; c; d]         then "all values are the same"
        elif same[a; c] && same[b; d] then "tuples are the same"
        elif same[a; c]               then "first values are the same"
        else                                "none of above"
    

    您可以将elif 更改为match,但对我来说似乎不可行。

    【讨论】:

    • 被否决(至少是 IMO),放弃了 F# 模式匹配的优雅,转而使用 if 和 else if 链式的命令式风格,这完全错过了函数式编程的要点。
    • @DavidArno 感谢您的反馈,但if-then-else 的概念甚至存在于范畴论和 lambda 演算中。您能否扩展一下它为什么以及如何损害功能范式?此外,match 只不过是if-then 的有序序列。
    【解决方案4】:

    在实践中,我可能会预先解包元组,然后执行一系列 if / then / else 表达式:

    let a,b = tuple1
    let c,d = tuple2
    
    if a = b && b = c && c = d then "all values are the same"
    elif a = c && b = d then "tuples are the same"
    elif a = c then "first values are the same"
    ...
    

    如果您发现自己经常这样做,则可能需要使用活动模式(在 2 元组的情况下,complete active pattern 是可行的并且可能更可取 - 穷举匹配比非穷举匹配“更安全”) .或者,也许您需要更复杂的数据结构。

    【讨论】:

    • 被否决(至少是 IMO),放弃了 F# 模式匹配的优雅,转而采用 if 和 else if 链式的命令式风格,这完全错过了函数式编程的要点。
    • @DavidArno 请注意,在 F# 中,if-then-else 构造是函数表达式,而不是命令式语言中的语句。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-11
    • 2020-12-01
    • 1970-01-01
    • 2022-08-22
    相关资源
    最近更新 更多