活动模式

利用活动模式,可以定义对输入数据进行细分的命名分区,继而在模式匹配表达式中像可区分联合那样使用这些名称。 可使用活动模式以自定义方式为每个分区分解数据。

语法

// 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 绑定创建的函数称为活动识别器。

例如,请参考以下带参数的活动模式。

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.

使用部分活动模式时,有时各个选择可以是不相交的或互斥的,但它们不需要。 在下面的示例中,Square 模式和 Cube 模式并非不相交,因为某些数字既是平方数,又是立方数(例如 64)。 以下程序使用 AND 模式来组合 Square 和 Cube 模式。 它输出所有既是平方数又是立方数的整数(最多 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 活动模式。 整数活动模式用于将匹配的字符串转换为可传递给 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

另请参阅