Неполное сопоставление с шаблоном, когда два шаблона используют предложение `when`

Обычным сюрпризом для начинающих программистов на F# является тот факт, что следующее является неполным совпадением:

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 возвращает строку "Нормальное дерево" для обоих sapling а также twoLeafTree деревья.

В случае, когда match выражение не содержит ничего, кроме when выражения (как в первом примере, где x а также y сравниваются), вполне разумно, чтобы компилятор F# не мог определить, будет ли совпадение выполнено. В конце концов, x а также y могут быть типы со "странной" реализацией сравнения и равенства, где ни одна из этих трех ветвей не является истинной.*

Но в таких случаях, как мой describe функция, почему F# компилятор не смотрит на шаблон, скажем: "Если все when выражения оцениваются в false, все равно будет полное совпадение "и пропущено предупреждение" о неполных совпадениях с образцами "? Есть ли какая-то конкретная причина для появления этого предупреждения здесь, или это просто случай, когда компилятор F# немного упрощен и дает ложное предупреждение, потому что его код не был достаточно сложным?

* На самом деле, можно установить x а также y до таких значений, что x < y, x = y, а также x > y все ложные, не выходя за "нормальные" границы стандартной системы типов.Net. В качестве специального бонусного вопроса / головоломки, каковы эти значения x а также y? Никаких пользовательских типов не нужно, чтобы ответить на эту головоломку; все, что вам нужно, это типы, представленные в стандартном.Net.

2 ответа

Решение

В F# match синтаксис when Охранники применяются ко всем перечисленным случаям, а не только к последнему.

В вашем конкретном сценарии охранник when saplingsGetSpecialTreatment относится к обоим Leaf n а также Branch [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

Просто уточняю пост Федора с дополнительным примером. Думайте об этом как о секции 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"
Другие вопросы по тегам