Поделиться через


Активные шаблоны (F#)

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

// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier1|identifier2|...|_|) [ arguments ] = expression

Заметки

В предыдущей синтаксической конструкции идентификаторы — это имена разделов входных данных, представленные arguments, или, иными словами, имена подмножеств набора всех значений аргументов. В определении активного шаблона может быть до семи разделов. expression описывает форму компонентов, на которые разбиваются данные. Можно использовать активное определение шаблона, чтобы назначить правила, позволяющие определить, к каким именованным разделам относятся значения, данные как аргументы. Символы (| и |) называются полукруглыми двойными скобками, а функция, созданная этим типом привязки 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
 R: 255 G: 0 B: 0
 H: 0.000000 S: 1.000000 B: 0.500000
Black
 R: 0 G: 0 B: 0
 H: 0.000000 S: 0.000000 B: 0.000000
White
 R: 255 G: 255 B: 255
 H: 0.000000 S: 0.000000 B: 1.000000
Gray
 R: 128 G: 128 B: 128
 H: 0.000000 S: 0.000000 B: 0.501961
BlanchedAlmond
 R: 255 G: 235 B: 205
 H: 36.000000 S: 1.000000 B: 0.901961

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

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

Частичные активные шаблоны

Иногда необходимо разделить на разделы только часть пространства входных данных. В этом случае создается набор частичных шаблонов, каждый из которых соответствует определенной части входных данных, но не соответствует другим частям. Активные шаблоны, которые не всегда производят значение, называются частичными активными шаблонами; они возвращают значение, которое имеет тип option. Чтобы определить частичный активный шаблон, нужно использовать подстановочный знак (_) в конце списка шаблонов, заключенного в полукруглые двойные скобки. В следующем примере кода демонстрируется использование частичного активного шаблона.

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

let err = 1.e-10

let floatequal x y =
   abs (x - y) < err

let (|Square|_|) (x : int) =
  if floatequal (sqrt (float x)) (round (sqrt (float x))) then Some(x)
  else None

let (|Cube|_|) (x : int) =
  if floatequal ((float x) ** ( 1.0 / 3.0)) (round ((float x) ** (1.0 / 3.0))) then Some(x)
  else None

let examineNumber x =
   match x with
      | Cube x -> printfn "%d is a cube" x
      | _ -> ()
   match x with
      | Square x -> printfn "%d is a square" x
      | _ -> ()

let findSquareCubes x =
   if (match x with
       | Cube x -> true
       | _ -> false
       &&
       match x with
       | Square x -> true
       | _ -> false
      )
   then printf "%d \n" x

[ 1 .. 1000000 ] |> List.iter (fun elem -> findSquareCubes elem)

Выходные данные выглядят следующим образом.

1
64
729
4096
15625
46656
117649
262144
531441
1000000

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

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

См. также

Ссылки

Выражения match (F#)

Другие ресурсы

Справочник по языку F#