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


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

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

// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier|_|) [ 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 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 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#