С помощью необязательного оператора when можно установить дополнительные условия или ограничения паттернов:
match выражение with | шаблон_1 [ when ограничение ] -> действия_1 | шаблон_2 [ when ограничение ] -> действие_2
Если для шаблона установлено ограничение, то сравниваемое выражение должно соответствовать шаблон и также соответствовать ограничению этого шаблона. Например:
let checkAge person =
match person with
| (_, age) when age > 110 || age < 1 -> "Недействительный возраст" // если age больше 110 и меньше 1
| (_, age) when age >=1 && age<18 -> "Доступ запрещен" // если возраст равен или больше 1 и меньше 18
| _ -> "Доступ разрешен" // в остальных случаях
printfn "%s" (checkAge ("Tom", 39)) // Доступ разрешен
printfn "%s" (checkAge ("Bob", 100500)) // Недействительный возраст
printfn "%s" (checkAge ("Sam", 16)) // Доступ запрещен
Допустим, извне передаются данные пользователя в виде кортежа с именем и возрастом, и в зависимости от возраста нам надо разрешить или запретить доступ к некоторым ресурсам. С помощью паттерна кортежей мы можем получить данные, в частности, возраст в переменную. А с помощью оператора when вводим ограничение для значения возраста. Так, следующий паттерн сопоставляется с кортежем, если второй элемент кортежа больше 110 или меньше 1:
| (_, age) when age > 110 || age < 1 -> "Недействительный возраст"
Аналогично работают другие шаблоны.
Другой пример. По умолчанию F# не предоставляет встроенной функции по удалению элемента из списка. Но мы можем написать свою:
let mutable numbers = [1; 2; 3; 4; 5]
let rec remove lst value =
match lst with
| head :: tail when head=value -> tail
| head :: tail -> head :: (remove tail value)
| _ -> []
// удаляем число 3
numbers <- remove numbers 3
printfn "%A" numbers // [1; 2; 4; 5]
Здесь определена рекурсивная функция remove, которая принимает список (lst) и удаляемое значение (value). С помощью pattern matching раскладываем список на первый элемент и список последующих элементов
head :: tail
Первый шаблон проверяет соответствие первого элемента удаляемому значению value. И если первый элемент равен value, то возвращаем список последующих элементов:
| head :: tail when head=value -> tail
Второй шаблон применяется, если первый элемент (head) НЕ равен value. В этом случае возвращаем список, который состоит из первого элемента и результата применения функции remove к списку tail:
| head :: tail -> head :: (remove tail value)
Если в списке нет элементов, то возвращаем пустой список
| _ -> []
Другой более сложный пример с проверкой свойств:
type Person(name: string) =
member this.Name = name
type Employee(name: string, company: string) =
inherit Person(name)
member this.Company = company
let printPerson (p: Person) =
match p with
| :? Employee as emp when emp.Company = "LocalComp" -> printfn "%s работает в %s" emp.Name emp.Company
| :? Employee as emp -> printfn "%s где-то работает" emp.Name
| _ -> printfn "%s не работает" p.Name
let tom = Person("Tom")
let bob = Employee("Bob", "LocalComp")
let sam = Employee("Sam", "GlobalComp")
printPerson tom // Tom не работает
printPerson bob // Bob работает в LocalComp
printPerson sam // Sam где-то работает
Здесь у нас есть класс человека - класс Person и класс-наследник Employee, который представляет работника. В функции printPerson получаем объект Person и смотрим, какой он тип представляет. Если он представляет тип Employee и его свойство Company равно "LocalComp", то значение сопоставляется со следующим шаблоном:
:? Employee as emp when emp.Company = "LocalComp" -> printfn "%s работает в %s" emp.Name emp.Company
Для проверки компании здесь вводится ограничение when emp.Company = "LocalComp". Это позволяет разграничить работников определенной компании от остальных работников.