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

寵の児 提交于 2019-12-13 13:25:49

问题


A common surprise for beginning F# programmers is the fact that the following is an incomplete match:

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

But I just encountered a situation that surprised me. Here's a small bit of sample code to demonstrate it:

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]"

This version of the describe function produced the "Incomplete pattern matches on this expression" warning, even though the pattern match is, in fact, complete. There are no possible trees that will not be matched by that pattern match, as can be seen by removing the specific branch of the match that had a when expression in it:

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

This version of describe returns the "Normal tree" string for both the sapling and twoLeafTree trees.

In the case where the match expression contains nothing but when expressions (like the first example where x and y are being compared), it is reasonable that the F# compiler might not be able to tell whether the match will be complete. After all, x and y might be types with a "weird" implementation of comparison and equality where none of those three branches are true.*

But in cases like my describe function, why doesn't the F# compiler look at the pattern, say "If all the when expressions evaluated to false, there would still be a complete match" and skip the "incomplete pattern matches" warning? Is there some specific reason for this warning showing up here, or is it just a case of the F# compiler being just a little bit simplistic here and giving a false-positive warning because its code wasn't sophisticated enough?

* In fact, it is possible to set x and y to values such that x < y, x = y, and x > y are all false, without ever stepping outside the "normal" bounds of the standard .Net type system. As a special bonus question/puzzle, what are these values of x and y? No custom types needed to answer this puzzle; all you need is types provided in standard .Net.


回答1:


In F# match syntax, the when guards apply to all cases enumerated just before it, not just to the last one.

In your specific scenario, the guard when saplingsGetSpecialTreatment applies to both Leaf n and Branch [Leaf n] cases. So this match will fail for the case when tree = Leaf 42 && saplingsGetSpecialTreatment = false

This would be complete:

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



回答2:


Just clarifying Fyodor's post with an extra example. Think of it as a when y = 3 section, an otherwise section, and then, for everything else

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"]

So, is this a sensible default? I think so.

Here's a more explicit version:

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)

... and a more compact version:

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


来源:https://stackoverflow.com/questions/43455264/incomplete-pattern-match-when-two-patterns-share-a-when-clause

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!