F# 如何匹配一个值的多个副本?
F#的模式匹配功能非常强大,因此编写以下代码感觉很自然:F# 如何匹配一个值的多个副本?,f#,pattern-matching,F#,Pattern Matching,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 t
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#的“活动模式”的完美用例。您可以这样定义其中的两个:
let (|Same|_|) (a, b) =
if a = b then Some a else None
let (|FstEqual|_|) ((a, _), (c, _)) =
if a = c then Some a else None
然后清理与它们匹配的模式;注意第一种情况(所有值都相等)如何使用嵌套的相同的模式来检查元组的第一个和第二个元素是否相等:
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指令),所以内联它们并避免函数调用的开销是有意义的。您可以用它来解决这个问题
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
特别是,您应该以文字(字符、字符串等)的形式定义模式
实际上,我可能会先解压缩元组,然后执行一系列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元组的情况下,a是可行的,并且可能更可取-穷举匹配比非穷举匹配“更安全”)。或者,您可能需要更复杂的数据结构。我认为,最优雅的方式可以通过结合@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
更改为匹配
,但对我来说似乎不可行。仅为了消除一些等式检查,就需要引入大量开销。如果仅执行此匹配
一次或两次,可能不会引起注意,但如果在循环某些数据时使用此选项(例如),则肯定会影响性能。同意。当可读性是关键,性能不是关键问题时,这是一个很好的方法。对于任何感兴趣的人来说:“F#的模式匹配非常强大”。F#继承的模式匹配的ML风格实际上故意没有那么强大。具体来说,ML将模式限制为所谓的线性模式,以确保匹配模式所需的时间量受模式大小的限制。在f*中,活动模式绕过这个,让你在模式匹配的中间做任何事情。缺点是性能不太可预测。Mathematica将这一点发挥到了极致。被否决的是(至少在我看来),抛弃了F#的模式匹配的优雅,转而支持非直接风格的链式if,else if's,完全忽略了函数式编程的意义。@DavidArno感谢您的反馈,但是,if-then-else
概念甚至存在于范畴论和lambda演算中。你能解释一下为什么它会损害功能范式吗?而且,匹配
不过是一个有序的if-then
序列。被降级为(至少在IMO),抛弃了F#模式匹配的优雅,转而支持链接if,else-if的基本风格,完全忽略了函数式编程的要点。@DavidArno注意到,在F#if-then-else
中,构造是函数表达式,而不是命令式语言中的语句。
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"