Паттерны переменных (variable pattern) позволяют присвоить сопоставляемое значение переменной, которую затем можно использовать в выражении справа от оператор ->. Сам по себе шаблон переменной соответствует любому вводу, но шаблоны переменных часто применяются внутри других шаблонов, что позволяет разложить на переменные более сложные структуры, такие как кортежи и массивы.
Например, если варианты объединений определяют некоторые поля, то мы можем получить эти поля в переменные:
type Contact =
| Email of emailAddress:string
| Phone of phoneNumber:string
| Post of city:string * street:string * building:string
let printContact contact =
match contact with
| Email emailAddr -> printfn $"Email: {emailAddr}"
| Phone phoneNumber -> printfn $"Тел.: {phoneNumber}"
| Post (city = c; street = s; building = b) -> printfn $"Адрес офиса: город {c}, {s}, {b}"
let contact1 = Email("some@xyzmail.com")
let contact2 = Phone("+79876543210")
let contact3 = Post("Минас-Тирит", "ул. Вязов", "д.13")
printContact contact1
printContact contact2
printContact contact3
Здесь объединение Contact определяет ряд вариантов, которые имеют поля. В конструкции match получаем эти поля в переменные. Если вариант объединения имеет только одно поле, то для его можно указать переменную справа от названия варианта:
Email emailAddr -> printfn $"Email: {emailAddr}"
В данном случае поле emailAddress варианта Email попадает в переменную emailAddr.
Если полей несколько, то можно их получить как части кортежа:
Post (city = c; street = s; building = b) -> printfn $"Адрес офиса: город {c}, {s}, {b}"
Здесь поле city попадает в переменную с, поле street - в переменную s, поле building - в переменную b.
Консольный вывод программы:
Email: some@xyzmail.com Тел.: +79876543210 Адрес офиса: город Минас-Тирит, ул. Вязов, д.13
Паттерн типов (type pattern) применяется для проверки типа выражения и обычно используется для проверки принадлежности производным типам:
type A() = class end
type B() = inherit A()
type C() = inherit A()
let checkA (a: A) =
match a with
| :? B -> printfn "a is B"
| :? C -> printfn "a is C"
| _ -> ()
let obj1: A = B()
let obj2: A = C()
checkA obj1 // a is B
checkA obj2 // a is C
Здесь определан примитивнейшая иерархия классов, где есть базовый тип - A и есть производные типы B и C. Для проверки типа перед шаблоном типа указываем оператор :?:
:? B -> printfn "a is B"
Более практический пример:
type Person(name: string) =
member this.Name = name
type Employee(name: string, company: string) =
inherit Person(name)
member this.Company = company
type Student(name: string, university: string) =
inherit Person(name)
member this.University = university
let printPerson (p: Person) =
match p with
| :? Employee as emp -> printfn "%s работает в %s" emp.Name emp.Company
| :? Student as st -> printfn "%s учится в %s" st.Name st.University
| _ -> printfn "%s не работает и не учится" p.Name
let obj1 = Person("Tom")
let obj2 = Employee("Bob", "LocalComp")
let obj3 = Student("Sam", "СуперУнивер")
printPerson obj1 // Tom не работает и не учится
printPerson obj2 // Bob работает в LocalComp
printPerson obj3 // Sam учится в СуперУнивер
Здесь есть базовый класс Person. От него наследуются классы Employee (класс работника) и Student (класс учащегося). Эти классы добавляют к унаследованному функционалу разные свойства. И, допустим, в функции printPerson мы хотим вывести на консоль информацию об объекте Person. Но поскольку этот объект в реальности может представлять и типы Employee и Student, то применяем паттерн типов
| :? Employee as emp -> printfn "%s работает в %s" emp.Name emp.Company
Кроме того, здесь применяется выражение as, которое привязывает сопоставленное значение к идентификатору (в данном случае emp). Далее мы можем использовать идентификатор emp справа от оператора ->, и этот идентификатор будет рассматриваться как значение типа Employee.