Активные шаблоны

Активные шаблоны позволяют определить именованные секции, которые подделят входные данные, чтобы эти имена можно было использовать в выражении сопоставления шаблонов точно так же, как для размеченного объединения. Активные шаблоны можно использовать для разложения данных в настраиваемом порядке для каждого раздела.

Синтаксис

// 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

Remarks

В предыдущем синтаксисе идентификаторы представляют собой имена секций входных данных, представленных аргументами, или, иными словами, имена подмножеств набора всех значений аргументов. В определении активного шаблона может быть до семи секций. Выражение описывает форму, в которую разбиваются данные. Определение активного шаблона можно использовать для определения правил, определяющих, какие из именованных секций являются значениями, заданными в качестве аргументов. Символы (| и |) называются скрепками в виде полукруглого символа, а функция, созданная с помощью этого типа привязки let, называется активным распознавателем.

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

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. Следующая программа использует шаблон и для объединения квадратных и шаблонных шаблонов Куба. Он выводит все целые числа до 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

Параметризованные активные шаблоны

Активные шаблоны всегда принимают по крайней мере один аргумент для сопоставляемого элемента, но они могут также принимать дополнительные аргументы, в этом случае применяется к параметризованному активному шаблону Name. Дополнительные аргументы позволяют специализированные шаблоны. Например, активные шаблоны, использующие регулярные выражения для анализа строк, часто содержат регулярное выражение в качестве дополнительного параметра, как в следующем коде, который также использует частичный активный шаблон, 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.

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.

См. также