Сопоставление шаблонов

Шаблоны — это правила преобразования входных данных. Они используются на языке F# для сравнения данных с логической структурой или структурами, декомпилировать данные на составные части или извлекать информацию из данных различными способами.

Комментарии

Шаблоны используются во многих языковых конструкциях, таких как match выражение. Они используются при обработке аргументов для функций в let привязках, лямбда-выражениях и в обработчиках исключений, связанных с выражением try...with . Дополнительные сведения см. в разделе "Выражения сопоставления", "Пусть привязки", "Лямбда-выражения": ключевое fun слово и исключения: выражениеtry...with.

Например, в match выражении шаблон соответствует символу канала.

match expression with
| pattern [ when condition ] -> result-expression
...

Каждый шаблон действует как правило для преобразования входных данных каким-то образом. match В выражении каждый шаблон в свою очередь проверяется, совместимы ли входные данные с шаблоном. Если совпадение найдено, выполняется выражение результата. Если совпадение не найдено, проверяется следующее правило шаблона. Необязательный, если часть условия описана в выражениях соответствия.

Поддерживаемые шаблоны показаны в следующей таблице. Во время выполнения входные данные проверяются на соответствие каждому из следующих шаблонов в порядке, указанном в таблице, и шаблоны применяются рекурсивно, от первого до последнего, когда они отображаются в коде, и слева направо для шаблонов в каждой строке.

Имя Описание Пример
Шаблон константы Любой числовый, символьный или строковый литерал, константу перечисления или определенный идентификатор литерала 1.0, "test", 30, Color.Red
Шаблон идентификатора Значение регистра дискриминированного объединения, метки исключения или активного варианта шаблона Some(x)

Failure(msg)
Шаблон переменной identifier a
Шаблон as шаблон в качестве идентификатора (a, b) as tuple1
Шаблон OR pattern1 | pattern2 ([h] | [h; _])
Шаблон AND pattern1&pattern2 (a, b) & (_, "test")
Шаблон минусов identifier :: list-identifier h :: t
Шаблон списка [ pattern_1; ... ; ; pattern_n ] [ a; b; c ]
Шаблон массива [| pattern_1; ..; pattern_n | ] [| a; b; c |]
Шаблон в круглых скобках ( шаблон ) ( a )
Шаблон кортежа ( pattern_1, ... , pattern_n ) ( a, b )
Шаблон записи { identifier1 = pattern_1; ... ; = identifier_n pattern_n } { Name = name; }
Шаблон подстановочных знаков _ _
Шаблон вместе с заметкой типа pattern : type a : int
Шаблон теста типа :? type [ as identifier ] :? System.DateTime as dt
Шаблон NULL null null
Шаблон Nameof nameof expr nameof str

Постоянные шаблоны

Шаблоны констант — числовые, символьные и строковые литералы, константы перечисления (с включенным именем типа перечисления). match Выражение, которое имеет только постоянные шаблоны, можно сравнить с оператором case на других языках. Входные данные сравниваются с литеральным значением и шаблоном совпадают, если значения равны. Тип литерала должен быть совместим с типом входных данных.

В следующем примере демонстрируется использование литеральных шаблонов, а также используется шаблон переменной и шаблон OR.

[<Literal>]
let Three = 3

let filter123 x =
    match x with
    // The following line contains literal patterns combined with an OR pattern.
    | 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
    // The following line contains a variable pattern.
    | var1 -> printfn "%d" var1

for x in 1..10 do filter123 x

Еще одним примером литерального шаблона является шаблон на основе констант перечисления. При использовании констант перечисления необходимо указать имя типа перечисления.

type Color =
    | Red = 0
    | Green = 1
    | Blue = 2

let printColorName (color:Color) =
    match color with
    | Color.Red -> printfn "Red"
    | Color.Green -> printfn "Green"
    | Color.Blue -> printfn "Blue"
    | _ -> ()

printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue

Шаблоны идентификаторов

Если шаблон представляет собой строку символов, образующих допустимый идентификатор, форма идентификатора определяет, как сопоставляется шаблон. Если идентификатор длиннее одного символа и начинается с прописного символа, компилятор пытается сопоставить шаблон идентификатора. Идентификатором этого шаблона может быть значение, помеченное атрибутом Литерала, дискриминированным случаем объединения, идентификатором исключения или активным регистром шаблона. Если совпадающий идентификатор не найден, совпадение завершается ошибкой, и следующее правило шаблона, шаблон переменной сравнивается с входными данными.

Шаблоны различающегося объединения могут быть простыми именованными случаями или иметь значение или кортеж, содержащий несколько значений. Если имеется значение, необходимо указать идентификатор значения. В случае кортежа необходимо указать шаблон кортежа с идентификатором для каждого элемента кортежа или идентификатора с именем поля для одного или нескольких именованных полей объединения. Примеры кода см. в этом разделе.

Тип option — это различающееся объединение, которое имеет два случая и SomeNone. Один случай (Some) имеет значение, но другой (None) — это просто именованный регистр. Таким образом, необходимо иметь переменную для значения, Some связанного Some с делом, но None должен отображаться сама по себе. В следующем коде переменной var1 присваивается значение, полученное путем сопоставления с делом Some .

let printOption (data : int option) =
    match data with
    | Some var1  -> printfn "%d" var1
    | None -> ()

В следующем примере PersonName различающееся объединение содержит сочетание строк и символов, представляющих возможные формы имен. Случаи дискриминированного объединения: FirstOnly, LastOnlyи FirstLast.

type PersonName =
    | FirstOnly of string
    | LastOnly of string
    | FirstLast of string * string

let constructQuery personName =
    match personName with
    | FirstOnly(firstName) -> printf "May I call you %s?" firstName
    | LastOnly(lastName) -> printf "Are you Mr. or Ms. %s?" lastName
    | FirstLast(firstName, lastName) -> printf "Are you %s %s?" firstName lastName

Для различающихся союзов, имеющих именованные поля, используйте знак равенства (=) для извлечения значения именованного поля. Например, рассмотрим дискриминационное объединение с объявлением, как показано ниже.

type Shape =
    | Rectangle of height : float * width : float
    | Circle of radius : float

Именованные поля можно использовать в выражении сопоставления шаблонов, как показано ниже.

let matchShape shape =
    match shape with
    | Rectangle(height = h) -> printfn $"Rectangle with length %f{h}"
    | Circle(r) -> printfn $"Circle with radius %f{r}"

Использование именованного поля является необязательным, поэтому в предыдущем примере оба Circle(r) и Circle(radius = r) имеют одинаковый эффект.

При указании нескольких полей используйте точку с запятой (;) в качестве разделителя.

match shape with
| Rectangle(height = h; width = w) -> printfn $"Rectangle with height %f{h} and width %f{w}"
| _ -> ()

Активные шаблоны позволяют определить более сложное сопоставление пользовательских шаблонов. Дополнительные сведения о активных шаблонах см. в разделе "Активные шаблоны".

Случай, когда идентификатор является исключением, используется в сопоставлении шаблонов в контексте обработчиков исключений. Сведения о сопоставлении шаблонов в обработке исключений см. в разделе "Исключения: try...with выражение".

Шаблоны переменных

Шаблон переменной присваивает значение, соответствующее имени переменной, которое затем доступно для использования в выражении выполнения справа от символа -> . Один шаблон переменной соответствует любому входу, но шаблоны переменных часто появляются в других шаблонах, что позволяет более сложные структуры, такие как кортежи и массивы, быть разложены на переменные.

В следующем примере показан шаблон переменной в шаблоне кортежа.

let function1 x =
    match x with
    | (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
    | (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
    | (var1, var2) -> printfn "%d equals %d" var1 var2

function1 (1,2)
function1 (2, 1)
function1 (0, 0)

как шаблон

Шаблон as — это шаблон, в as который добавляется предложение. Предложение as привязывает соответствующее значение к имени, которое может использоваться в выражении match выполнения выражения выражения или, в случае, когда этот шаблон используется в let привязке, имя добавляется в качестве привязки к локальной области.

В следующем примере используется as шаблон.

let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1

ИЛИ шаблон

Шаблон OR используется, если входные данные могут соответствовать нескольким шаблонам, и вы хотите выполнить один и тот же код в результате. Типы обеих сторон шаблона OR должны быть совместимыми.

В следующем примере показан шаблон OR.

let detectZeroOR point =
    match point with
    | (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
    | _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)

И шаблон

Шаблон AND требует, чтобы входные данные соответствовали двум шаблонам. Типы обеих сторон шаблона AND должны быть совместимыми.

В следующем примере detectZeroTuple показано в разделе "Шаблон кортежа" далее в этом разделе, но здесь оба var2var1 и получены в виде значений с помощью шаблона AND.

let detectZeroAND point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
    | (var1, var2)  & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
    | _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)

Шаблон минусов

Шаблон минусов используется для разложения списка на первый элемент, голову и список, содержащий остальные элементы, хвост.

let list1 = [ 1; 2; 3; 4 ]

// This example uses a cons pattern and a list pattern.
let rec printList l =
    match l with
    | head :: tail -> printf "%d " head; printList tail
    | [] -> printfn ""

printList list1

Шаблон списка

Шаблон списка позволяет разложить списки на ряд элементов. Сам шаблон списка может соответствовать только спискам определенного количества элементов.

// This example uses a list pattern.
let listLength list =
    match list with
    | [] -> 0
    | [ _ ] -> 1
    | [ _; _ ] -> 2
    | [ _; _; _ ] -> 3
    | _ -> List.length list

printfn "%d" (listLength [ 1 ])
printfn "%d" (listLength [ 1; 1 ])
printfn "%d" (listLength [ 1; 1; 1; ])
printfn "%d" (listLength [ ] )

Шаблон массива

Шаблон массива похож на шаблон списка и может использоваться для разложения массивов определенной длины.

// This example uses array patterns.
let vectorLength vec =
    match vec with
    | [| var1 |] -> var1
    | [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
    | [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
    | _ -> failwith (sprintf "vectorLength called with an unsupported array size of %d." (vec.Length))

printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )

Круглые скобки

Круглые скобки можно сгруппировать по шаблонам для достижения желаемой ассоциативности. В следующем примере скобки используются для управления ассоциативностью между шаблоном AND и шаблоном минусов.

let countValues list value =
    let rec checkList list acc =
       match list with
       | (elem1 & head) :: tail when elem1 = value -> checkList tail (acc + 1)
       | head :: tail -> checkList tail acc
       | [] -> acc
    checkList list 0

let result = countValues [ for x in -10..10 -> x*x - 4 ] 0
printfn "%d" result

Шаблон кортежа

Шаблон кортежа соответствует входным данным в форме кортежа и позволяет разложить кортеж на составляющие элементы с помощью переменных сопоставления шаблонов для каждой позиции кортежа.

В следующем примере демонстрируется шаблон кортежа, а также используются литеральные шаблоны, шаблоны переменных и шаблон подстановочных знаков.

let detectZeroTuple point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (0, var2) -> printfn "First value is 0 in (0, %d)" var2
    | (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
    | _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)

Шаблон записи

Шаблон записи используется для разложения записей для извлечения значений полей. Шаблон не должен ссылаться на все поля записи; все пропущенные поля просто не участвуют в сопоставлении и не извлекаются.

// This example uses a record pattern.

type MyRecord = { Name: string; ID: int }

let IsMatchByName record1 (name: string) =
    match record1 with
    | { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
    | _ -> false

let recordX = { Name = "Parker"; ID = 10 }
let isMatched1 = IsMatchByName recordX "Parker"
let isMatched2 = IsMatchByName recordX "Hartono"

Шаблон подстановочных знаков

Шаблон подстановочного знака представлен символом подчеркивания (_) и соответствует любому входу, аналогично шаблону переменной, за исключением того, что входные данные удаляются вместо присвоения переменной. Шаблон подстановочных знаков часто используется в других шаблонах в качестве заполнителя для значений, которые не нужны в выражении справа от символа -> . Шаблон с подстановочными знаками также часто используется в конце списка шаблонов для сопоставления любых несовпаденных входных данных. Шаблон с подстановочными знаками демонстрируется во многих примерах кода в этом разделе. Пример см. в приведенном выше коде.

Шаблоны с заметками типов

Шаблоны могут иметь заметки типа. Они ведут себя так же, как другие заметки типов и вывод руководства, как и другие заметки типов. Круглые скобки требуются вокруг заметок типов в шаблонах. В следующем коде показан шаблон с заметкой типа.

let detect1 x =
    match x with
    | 1 -> printfn "Found a 1!"
    | (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1

Шаблон теста типа

Шаблон теста типа используется для сопоставления входных данных с типом. Если входной тип соответствует типу (или производного типа), указанному в шаблоне, совпадение завершается успешно.

В следующем примере показан шаблон теста типа.

open System.Windows.Forms

let RegisterControl(control:Control) =
    match control with
    | :? Button as button -> button.Text <- "Registered."
    | :? CheckBox as checkbox -> checkbox.Text <- "Registered."
    | _ -> ()

Если вы проверяете, относится ли идентификатор к определенному производному типу, вам не нужна as identifier часть шаблона, как показано в следующем примере:

type A() = class end
type B() = inherit A()
type C() = inherit A()

let m (a: A) =
    match a with
    | :? B -> printfn "It's a B"
    | :? C -> printfn "It's a C"
    | _ -> ()

Шаблон NULL

Шаблон NULL соответствует значению NULL, которое может отображаться при работе с типами, допускающими значение NULL. Шаблоны NULL часто используются при взаимодействии с платформа .NET Framework кодом. Например, возвращаемое значение API .NET может быть входным для match выражения. Вы можете управлять потоком программы на основе того, равно ли возвращаемое значение null, а также на других характеристиках возвращаемого значения. Вы можете использовать шаблон NULL, чтобы запретить распространение значений NULL на остальную часть программы.

В следующем примере используется шаблон NULL и шаблон переменной.

let ReadFromFile (reader : System.IO.StreamReader) =
    match reader.ReadLine() with
    | null -> printfn "\n"; false
    | line -> printfn "%s" line; true

let fs = System.IO.File.Open("..\..\Program.fs", System.IO.FileMode.Open)
let sr = new System.IO.StreamReader(fs)
while ReadFromFile(sr) = true do ()
sr.Close()

Шаблон Nameof

Шаблон nameof соответствует строке, если его значение равно выражению, которое следует ключевому слову nameof . Например:

let f (str: string) =
    match str with
    | nameof str -> "It's 'str'!"
    | _ -> "It is not 'str'!"

f "str" // matches
f "asdf" // does not match

Сведения о том, что можно назвать, см. в nameof операторе.

См. также раздел