С помощью необязательного оператора 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"
. Это позволяет разграничить работников определенной компании от остальных работников.