当两个模式共享一个`when`子句

时间:2017-04-17 16:21:18

标签: f# pattern-matching guard-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函数产生了“此表达式上的不完整模式匹配”警告,即使模式匹配实际上已完成。没有可能的树与该模式匹配不匹配,可以通过删除其中包含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会返回saplingtwoLeafTree树的“普通树”字符串。

如果match表达式只包含when个表达式(如第一个比较xy的示例),则合理的是F#编译器可能无法判断匹配是否完整。毕竟,xy 可能是具有“奇怪”的比较和平等实现的类型,其中这三个分支都不是真的。*

但是在像我的describe函数这样的情况下,为什么F#编译器不会看模式,说“如果所有when表达式都被评估为false,那么仍然会有一个完整的匹配“并跳过”不完整的模式匹配“警告?这个警告是否有一些特定的原因出现在这里,或者仅仅是F#编译器在这里有点过分简单并给出误报警告,因为它的代码不够复杂?

*实际上,可以将xy设置为值x < yx = yx > y 全部 false,没有超出标准.Net类型系统的“正常”范围。作为一个特殊的奖励问题/谜题,xy的这些价值是什么?没有自定义类型来回答这个难题;您只需要标准.Net中提供的类型。

2 个答案:

答案 0 :(得分:8)

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

在您的特定情况下,警卫when saplingsGetSpecialTreatment适用于Leaf nBranch [Leaf n]个案件。因此,当tree = Leaf 42 && saplingsGetSpecialTreatment = false

时,此匹配将失败

这将是完整的:

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

答案 1 :(得分:1)

用一个额外的例子澄清Fyodor的帖子。把它想象成y = 3的部分,否则是部分,然后是其他所有部分

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"