Неполное сопоставление с шаблоном, когда два шаблона используют предложение `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"