Паттерны переменных (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.