Сопоставление шаблонов (F#)
Шаблоны — это правила для преобразования входных данных.Они используются во всем языке F# для сравнения данных с логической структурой или структурами, разложения данных на составные части или извлечения информации из данных различными способами.
Заметки
Шаблоны используются во многих конструкциях языка, таких как выражение match.Они используются при обработке аргументов для функций в привязках let, лямбда-выражениях и обработчиках исключений, связанных с выражением try...with.Дополнительные сведения см. в разделах Выражения match (F#), Привязки let (F#), Лямбда-выражения: ключевое слово fun (F#) и Исключения: выражение try...with (F#).
Например, в выражении match составляющая pattern — это то, что следует за символом вертикальной черты.
match expression with
| pattern [ если condition ] -> result-expression
...
Каждый шаблон выступает в качестве правила для преобразования входных данных тем или иным способом.В выражении match каждый шаблон поочередно анализируется на предмет совместимости входных данных с шаблоном.Если обнаружено соответствие, выражение-результат выполняется.Если соответствие не обнаружено, проверяется следующее правило-шаблон.Необязательная часть "when condition" рассматривается в разделе Выражения match (F#).
Поддерживаемые шаблоны приведены в следующей таблице.Во время выполнения входные данные сравниваются с каждым из следующих шаблонов в том порядке, в котором они перечислены в таблице, и шаблоны применяются рекурсивно, от первого до последнего, как они следуют в коде, и слева направо в случае шаблонов в каждой строке.
Имя |
Описание |
Пример |
---|---|---|
Шаблон константы |
Любой числовой, символьный или строковый литерал, константа перечисления или определенный литеральный идентификатор |
1.0, "test", 30, Color.Red |
Шаблон идентификатора |
Значение ветви размеченного объединения, метка исключения или ветвь активного шаблона |
Some(x) Failure(msg) |
Шаблон переменной |
identifier |
a |
Шаблон as |
шаблон as идентификатор |
(a, b) as tuple1 |
Шаблон OR |
pattern1 | pattern2 |
([h] | [h; _]) |
Шаблон AND |
шаблон1 & шаблон2 |
(a, b) & (_, "test") |
Шаблон cons-списка |
identifier :: list-identifier |
h :: t |
Шаблон списка |
[ шаблон_1; ...; шаблон_n ] |
[ a; b; c ] |
Шаблон массива |
[| шаблон_1; ..; шаблон_n ] |
[| a; b; c |] |
Шаблон в круглых скобках |
( pattern ) |
( a ) |
Шаблон кортежа |
( шаблон_1, ..., шаблон_n ) |
( a, b ) |
Шаблон записи |
{ идентификатор1 = шаблон_1; ...; идентификатор_n = шаблон_n } |
{ Name = name; } |
Шаблон подстановочного знака |
_ |
_ |
Шаблон вместе с аннотацией типа |
pattern : type |
a : int |
Шаблон проверки типа |
:?type [ as identifier ] |
:? System.DateTime as dt |
Шаблон NULL |
null |
null |
Шаблоны констант
Шаблоны констант — это числовые, символьные и строковые литералы, константы перечислений (включая имя типа перечисления).Выражение 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
Шаблоны идентификаторов
Если шаблон является строкой символов, представляющей допустимый идентификатор, форма идентификатора определяет способ подбора соответствующего шаблона.Если идентификатор длиннее одного символа и начинается с символа верхнего регистра, компилятор пытается найти соответствие шаблону идентификатора.Идентификатором для этого шаблона может быть значение, отмеченное атрибутом Literal, ветвь размеченного объединения, идентификатор исключения или ветвь активного шаблона.Если совпадающий идентификатор не найден, подбор завершается неудачей и с входными данными сравнивается следующее правило-шаблон — шаблон переменной.
Шаблоны размеченных объединений могут представлять собой простые именованные классы либо могут иметь значение или кортеж, содержащий несколько значений.Если имеется значение, необходимо указать идентификатор для значения или, в случае шаблона, необходимо предоставить шаблон кортежа с идентификатором для каждого элемента кортежа.Примеры см. в коде в этом разделе.
Тип option — это размеченное объединение, имеющее две ветви: Some и None.Одна ветвь (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
Активные шаблоны позволяют определять более сложный настраиваемый подбор шаблона.Дополнительные сведения об активных шаблонах см. в разделе Активные шаблоны (F#).
Случай, когда идентификатором является исключение, используется при подборе шаблона в контексте обработчиков исключений.Сведения о подборе шаблона при обработке исключений см. в разделе Исключения: выражение try...with (F#).
Шаблоны переменных
Шаблон переменной присваивает сравниваемое значение имени переменной, которое затем может использоваться в выполняемом выражении справа от символа ->.Используемый отдельно, шаблон переменной соответствует любым входным данным, однако шаблоны переменных часто используются внутри других шаблонов, что позволяет разлагать на переменные более сложные структуры, такие как кортежи и массивы.
В следующем примере демонстрируется шаблон переменной внутри шаблона кортежа.
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.Выражение as привязывает подобранное значение к имени, которое можно использовать в выполняемом выражении выражения match или, когда этот шаблон используется в привязке let, имя добавляется в качестве привязки в локальную область видимости.
В следующем примере используется шаблон as.
let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1
Шаблон OR
Шаблон 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 требует, чтобы входные данные соответствовали двум шаблонам.Типы с обеих сторон шаблона AND должны быть совместимы.
Следующий пример аналогичен примеру detectZeroTuple, приведенному ниже в разделе Шаблон кортежа, однако в данном случае и переменная var1, и переменная var2 получаются как значения с помощью шаблона 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)
Шаблон cons-списка
Шаблон cons-списка используется для разложения списка на первый элемент — голову — и список, содержащий остальные элементы (хвост).
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 "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 и шаблоном cons-списка.
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."
| _ -> ()
Шаблон 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()