活动模式 (F#)
利用活动模式,您可以定义用于细分输入数据的命名分区,这样您就可以在模式匹配表达式中使用这些名称,就像为可区分联合使用名称一样。 可以使用活动模式按照自定义方式为每个分区分解数据。
// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier1|identifier2|...|_|) [ arguments ] = expression
备注
在前面的语法中,标识符是由 arguments 表示的输入数据的分区名称,或者换一种说法,标识符是参数的所有值的集的子集名称。 在主动模式定义中最多可有 7 个分区。 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
通过将上述活动模式的两种用法结合使用,您可以对数据进行分区并将数据分解为适当的形式,并按照最利于计算的形式对适当的数据执行相应的计算。
利用生成的模式匹配表达式,可以按照可读性非常好的简便方法写入数据,并大大简化可能存在的复杂分支和数据分析代码。
分部活动模式
有时,您只需要对部分输入空间进行分区。 在此情况下,可编写一组分部模式,其中的每个模式均匹配某些输入,但无法匹配其他输入。 并不总是生成值的活动模式被称作“分部活动模式”;这些模式具有一个属于选项类型的返回值。 若要定义分部活动模式,请在香蕉夹内的模式列表的结尾使用通配符 (_)。 下面的代码阐释了分部活动模式的用法。
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。 以下程序将输出 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