Активные шаблоны
Активные шаблоны позволяют определять именованные секции , которые подразделяют входные данные, чтобы использовать эти имена в выражении сопоставления шаблонов так же, как и для различаемого объединения. Активные шаблоны можно использовать для разложения данных в настраиваемом порядке для каждого раздела.
Синтаксис
// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression
// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression
// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
Замечания
В предыдущем синтаксисе идентификаторы — это имена для секций входных данных, представленных аргументами, или, другими словами, имена подмножества набора всех значений аргументов. В активном определении шаблона может быть до семи секций. Выражение описывает форму, в которую необходимо разложить данные. Можно использовать активное определение шаблона для определения правил определения того, к каким из именованных секций относятся значения, заданные в качестве аргументов. Символы (| и |) называются банановым клипами , а функция, созданная этим типом привязки, называется активным распознаватель.
В качестве примера рассмотрим следующий активный шаблон с аргументом.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
Активный шаблон можно использовать в выражении сопоставления шаблонов, как показано в следующем примере.
let TestNumber input =
match input with
| Even -> printfn "%d is even" input
| Odd -> printfn "%d is odd" input
TestNumber 7
TestNumber 11
TestNumber 32
Выходные данные этой программы приведены следующим образом:
7 is odd
11 is odd
32 is even
Другое использование активных шаблонов заключается в разложении типов данных несколькими способами, например, когда одни и те же базовые данные имеют различные возможные представления. Например, Color
объект может быть разложен в представление RGB или представление HSB.
open System.Drawing
let (|RGB|) (col : System.Drawing.Color) =
( col.R, col.G, col.B )
let (|HSB|) (col : System.Drawing.Color) =
( col.GetHue(), col.GetSaturation(), col.GetBrightness() )
let printRGB (col: System.Drawing.Color) =
match col with
| RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b
let printHSB (col: System.Drawing.Color) =
match col with
| HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b
let printAll col colorString =
printfn "%s" colorString
printRGB col
printHSB col
printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"
Выходные данные приведенной выше программы приведены следующим образом:
Red
Red: 255 Green: 0 Blue: 0
Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
Red: 0 Green: 0 Blue: 0
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
Red: 255 Green: 255 Blue: 255
Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
Red: 128 Green: 128 Blue: 128
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
Red: 255 Green: 235 Blue: 205
Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961
В сочетании эти два способа использования активных шаблонов позволяют секционировать и разлагать данные в соответствующую форму и выполнять соответствующие вычисления по соответствующим данным в форме, наиболее удобной для вычисления.
Результирующее выражение сопоставления шаблонов позволяет записывать данные удобным способом, который очень удобочитаемый, что значительно упрощает потенциально сложный код ветвления и анализа данных.
Частичные активные шаблоны
Иногда необходимо секционировать только часть входного пространства. В этом случае создается набор частичных шаблонов, каждый из которых соответствует некоторым входным данным, но не соответствует другим входным данным. Активные шаблоны, которые не всегда создают значение, называются частичными активными шаблонами; они имеют возвращаемое значение, которое является типом параметра. Чтобы определить частичный активный шаблон, вы используете дикий символ карта (_) в конце списка шаблонов внутри банановых клипов. Следующий код иллюстрирует использование частично активного шаблона.
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let (|Float|_|) (str: string) =
let mutable floatvalue = 0.0
if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
else None
let parseNumeric str =
match str with
| Integer i -> printfn "%d : Integer" i
| Float f -> printfn "%f : Floating point" f
| _ -> printfn "%s : Not matched." str
parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"
Выходные данные предыдущего примера приведены следующим образом:
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
При использовании частичных активных шаблонов иногда отдельные варианты могут быть несвязанными или взаимоисключающими, но они не должны быть. В следующем примере квадрат шаблона и куб шаблона не связаны, так как некоторые числа являются квадратами и кубами, например 64. Следующая программа использует шаблон AND для объединения шаблонов квадратов и кубов. Он выводит все целые числа до 1000, которые являются как квадратами, так и кубами, а также теми, которые являются только кубами.
let err = 1.e-10
let isNearlyIntegral (x:float) = abs (x - round(x)) < err
let (|Square|_|) (x : int) =
if isNearlyIntegral (sqrt (float x)) then Some(x)
else None
let (|Cube|_|) (x : int) =
if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
else None
let findSquareCubes x =
match x with
| Cube x & Square _ -> printfn "%d is a cube and a square" x
| Cube x -> printfn "%d is a cube" x
| _ -> ()
[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)
Вывод выглядит следующим образом.
1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube
Параметризованные активные шаблоны
Активные шаблоны всегда принимают по крайней мере один аргумент для соответствующего элемента, но они также могут принимать дополнительные аргументы, в этом случае применяется параметризованный активный шаблон имени. Дополнительные аргументы позволяют специализированным шаблоном. Например, активные шаблоны, использующие регулярные выражения для анализа строк, часто включают регулярное выражение в качестве дополнительного параметра, как в следующем коде, который также использует частичный активный шаблон Integer
, определенный в предыдущем примере кода. В этом примере строки, использующие регулярные выражения для различных форматов дат, предоставляются для настройки общего активного шаблона ParseRegex. Активный шаблон integer используется для преобразования сопоставленных строк в целые числа, которые можно передать в конструктор DateTime.
open System.Text.RegularExpressions
// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
match str with
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
-> new System.DateTime(y + 2000, m, d)
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
-> new System.DateTime(y, m, d)
| ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
-> new System.DateTime(y, m, d)
| _ -> new System.DateTime()
let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"
printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())
Выходные данные предыдущего кода приведены следующим образом:
12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM
Активные шаблоны не ограничиваются только выражениями сопоставления шаблонов, вы также можете использовать их для привязок let-bindings.
let (|Default|) onNone value =
match value with
| None -> onNone
| Some e -> e
let greet (Default "random citizen" name) =
printfn "Hello, %s!" name
greet None
greet (Some "George")
Выходные данные предыдущего кода приведены следующим образом:
Hello, random citizen!
Hello, George!
Обратите внимание, что параметризуются только одинарные активные шаблоны.
// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd
Представления структуры для частичных активных шаблонов
По умолчанию частичные активные шаблоны возвращают option
значение, которое будет включать выделение значения в Some
успешном совпадении. Кроме того, можно использовать параметр значения в качестве возвращаемого значения с помощью атрибута Struct
:
open System
[<return: Struct>]
let (|Int|_|) str =
match Int32.TryParse(str) with
| (true, n) -> ValueSome n
| _ -> ValueNone
Атрибут должен быть указан, так как использование возвращаемой структуры не выводится из простого изменения типа возвращаемого значения ValueOption
. Дополнительные сведения см. в статье RFC FS-1039.