当两个模式共享一个“when”子句时,模式匹配不完整

Incomplete pattern match when two patterns share a `when` clause

对于初学 F# 程序员的 common surprise 事实是以下是不完全匹配:

let x, y = 5, 10
match something with
| _ when x < y -> "Less than"
| _ when x = y -> "Equal"
| _ when x > y -> "Greater than"

但是我刚刚遇到了一个让我很意外的情况。下面是一小段示例代码来演示它:

type Tree =
| Leaf of int
| Branch of Tree list

let sapling = Branch [Leaf 1]  // Small tree with one leaf
let twoLeafTree = Branch [Leaf 1; Leaf 2]

let describe saplingsGetSpecialTreatment tree =
    match tree with
    | Leaf n
    | Branch [Leaf n] when saplingsGetSpecialTreatment ->
        sprintf "Either a leaf or a sapling containing %d" n
    | Branch subTree ->
        sprintf "Normal tree with sub-tree %A" subTree

describe true sapling // Result: "Either a leaf or a sapling containing 1"
describe false sapling // Result: "Normal tree with sub-tree [Leaf 1]"
describe true twoLeafTree // Result: "Normal tree with sub-tree [Leaf 1; Leaf 2]"
describe false twoLeafTree // Result: "Normal tree with sub-tree [Leaf 1; Leaf 2]"

这个版本的 describe 函数产生了 "Incomplete pattern matches on this expression" 警告,即使模式匹配实际上是完整的。没有可能的树不会被该模式匹配匹配,正如 删除 匹配的特定分支中有 when 表达式所示:

let describe tree =
    match tree with
    | Leaf n -> sprintf "Leaf containing %d" n
    | Branch subTree ->
        sprintf "Normal tree with sub-tree %A" subTree

此版本的 describe returns saplingtwoLeafTree 树的 "Normal tree" 字符串。

match 表达式只包含 when 表达式的情况下(就像第一个比较 xy 的例子),这是合理的F# 编译器可能无法判断匹配是否完成。毕竟,xy 可能 是具有 "weird" 比较和相等实现的类型,其中 none 这三个分支是是的。*

但是在像我的 describe 函数这样的情况下,为什么 F# 编译器不查看模式,例如 "If all the when expressions evaluated to false, there would still be a complete match" 并跳过 "incomplete pattern matches" 警告?此处出现此警告是否有某些特定原因,或者只是 F# 编译器在这里有点简单化并给出误报警告,因为它的代码不够复杂?

* 事实上,可以将 xy 设置为 x < yx = yx > y 的值 all false,从未超出标准 .Net 类型系统的 "normal" 范围。作为特别奖励question/puzzle,xy的这些值是多少?不需要自定义类型来回答这个难题;您所需要的只是标准 .Net 中提供的类型。

在 F# match 语法中,when 守卫适用于 所有 在它之前枚举的情况,而不仅仅是最后一个。

在您的特定情况下,守卫 when saplingsGetSpecialTreatment 适用于 Leaf nBranch [Leaf n] 两种情况。所以这个匹配在 tree = Leaf 42 && saplingsGetSpecialTreatment = false

的情况下会失败

以下内容就完成了,因为 Leaf 案例现在有了自己的分支:

let describe saplingsGetSpecialTreatment tree =
    match tree with
    | Leaf n ->
        sprintf "Either a leaf or a sapling containing %d" n
    | Branch [Leaf n] when saplingsGetSpecialTreatment ->
        sprintf "Either a leaf or a sapling containing %d" n
    | Branch subTree ->
        sprintf "Normal tree with sub-tree %A" subTree

只是用一个额外的例子来澄清 Fyodor 的 post。把它想象成一个 when y = 3 部分,一个 otherwise 部分,然后,对于其他一切

let f y x = 
  match x with
  | 0 
  | 1 
  | 2 when y = 3 -> "a"
  | 0
  | 1
  | 2            -> "b"
  | _            -> "c"

[0 .. 3] |> List.map (f 3)
[0 .. 3] |> List.map (f 2)

FSI

val f : y:int -> x:int -> string

> val it : string list = ["a"; "a"; "a"; "c"]

> val it : string list = ["b"; "b"; "b"; "c"]

那么,这是一个明智的默认设置吗?我认同。

这里有一个更明确的版本:

let f2 y x =
  match x,y with
  | 0,3
  | 0,3
  | 0,3 -> "a"
  | 0,_
  | 1,_
  | 2,_ -> "b"
  | _ -> "c"

[0 .. 3] |> List.map (f2 3)
[0 .. 3] |> List.map (f2 2)

...和更紧凑的版本:

let f3 y x = x |> function | 0 | 1 | 2 when y = 3 -> "a"
                           | 0 | 1 | 2 -> "b"
                           | _ -> "c"