F# 编程/Active Patterns
F# : Active Patterns |
Active Patterns 允许程序员将任意值包装到类似 联合 的数据结构中,以便于模式匹配。例如,可以使用 Active Pattern 包装 对象,以便像使用其他联合类型一样轻松地在模式匹配中使用对象。
Active Patterns 看起来像是具有特殊名称的函数
let (|name1|name2|...|) = ...
此函数定义了一个临时联合数据结构,其中每个联合情况 namen
由 |
分隔,整个列表包含在 (|
和 |)
之间(谦逊地称为“香蕉括号”)。换句话说,该函数根本没有简单的名称,而是定义了一系列联合构造函数。
典型的 Active Pattern 可能看起来像这样
let (|Even|Odd|) n =
if n % 2 = 0 then
Even
else
Odd
Even
和 Odd
是联合构造函数,因此我们的 Active Pattern 仅返回 Even
或 Odd
的实例。上面的代码大致等同于以下代码
type numKind =
| Even
| Odd
let get_choice n =
if n % 2 = 0 then
Even
else
Odd
Active Patterns 还可以定义包含一组参数的联合构造函数。例如,考虑我们可以用 Active Pattern 包装 seq<'a>
,如下所示
let (|SeqNode|SeqEmpty|) s =
if Seq.isEmpty s then SeqEmpty
else SeqNode ((Seq.head s), Seq.skip 1 s)
当然,此代码等同于以下代码
type seqWrapper<'a> =
| SeqEmpty
| SeqNode of 'a * seq<'a>
let get_choice s =
if Seq.isEmpty s then SeqEmpty
else SeqNode ((Seq.head s), Seq.skip 1 s)
您可能已经注意到 Active Patterns 和显式定义的联合之间的直接差异
- Active Patterns 定义了一个匿名联合,而显式联合有一个名称(
numKind
、seqWrapper
等)。 - Active Patterns 使用一种类型推断来确定其构造函数参数,而对于显式联合,我们需要为每种情况显式定义构造函数参数。
使用 Active Patterns 的语法看起来有点奇怪,但一旦您了解了它的含义,就很容易理解。Active Patterns 用于模式匹配表达式,例如
> let (|Even|Odd|) n =
if n % 2 = 0 then Even
else Odd
let testNum n =
match n with
| Even -> printfn "%i is even" n
| Odd -> printfn "%i is odd" n;;
val ( |Even|Odd| ) : int -> Choice<unit,unit>
val testNum : int -> unit
> testNum 12;;
12 is even
val it : unit = ()
> testNum 17;;
17 is odd
这里发生了什么?当模式匹配函数遇到 Even
时,它会调用 (|Even|Odd|)
,并将匹配子句中的参数传递给它,就好像您编写了以下代码一样
type numKind =
| Even
| Odd
let get_choice n =
if n % 2 = 0 then
Even
else
Odd
let testNum n =
match get_choice n with
| Even -> printfn "%i is even" n
| Odd -> printfn "%i is odd" n
匹配子句中的参数始终作为最后一个参数传递给 Active Pattern 表达式。使用我们之前关于 seq
的示例,我们可以编写以下代码
> let (|SeqNode|SeqEmpty|) s =
if Seq.isEmpty s then SeqEmpty
else SeqNode ((Seq.head s), Seq.skip 1 s)
let perfectSquares = seq { for a in 1 .. 10 -> a * a }
let rec printSeq = function
| SeqEmpty -> printfn "Done."
| SeqNode(hd, tl) ->
printf "%A " hd
printSeq tl;;
val ( |SeqNode|SeqEmpty| ) : seq<'a> -> Choice<('a * seq<'a>),unit>
val perfectSquares : seq<int>
val printSeq : seq<'a> -> unit
> printSeq perfectSquares;;
1 4 9 16 25 36 49 64 81 100 Done.
传统上,seq
难以进行模式匹配,但现在我们可以像操作列表一样轻松地操作它们。
可以向 Active Patterns 传递参数,例如
> let (|Contains|) needle (haystack : string) =
haystack.Contains(needle)
let testString = function
| Contains "kitty" true -> printfn "Text contains 'kitty'"
| Contains "doggy" true -> printfn "Text contains 'doggy'"
| _ -> printfn "Text neither contains 'kitty' nor 'doggy'";;
val ( |Contains| ) : string -> string -> bool
val testString : string -> unit
> testString "I have a pet kitty and she's super adorable!";;
Text contains 'kitty'
val it : unit = ()
> testString "She's fat and purrs a lot :)";;
Text neither contains 'kitty' nor 'doggy'
单例 Active Pattern (|Contains|)
包装了 String.Contains
函数。当我们调用 Contains "kitty" true
时,F# 将 "kitty"
和我们正在匹配的参数传递给 (|Contains|)
Active Pattern,并测试返回值是否等于 true
。上面的代码等同于
type choice =
| Contains of bool
let get_choice needle (haystack : string) = Contains(haystack.Contains(needle))
let testString n =
match get_choice "kitty" n with
| Contains(true) -> printfn "Text contains 'kitty'"
| _ ->
match get_choice "doggy" n with
| Contains(true) -> printfn "Text contains 'doggy'"
| printfn "Text neither contains 'kitty' nor 'doggy'"
如您所见,使用 Active Patterns 的代码比使用显式定义的联合的等效代码更简洁易读。
注意: 单例 Active Patterns 乍一看可能并不太有用,但它们可以帮助清理混乱的代码。例如,上面的 Active Pattern 包装了
String.Contains
方法,并允许我们在模式匹配表达式中调用它。如果没有 Active Pattern,模式匹配会很快变得混乱let testString = function | (n : string) when n.Contains("kitty") -> printfn "Text contains 'kitty'" | n when n.Contains("doggy") -> printfn "Text contains 'doggy'" | _ -> printfn "Text neither contains 'kitty' nor 'doggy'"
部分 Active Pattern 是一种特殊的单例 Active Pattern 类:它返回 Some
或 None
。例如,一个非常方便的用于处理正则表达式的 Active Pattern 可以定义如下
> let (|RegexContains|_|) pattern input =
let matches = System.Text.RegularExpressions.Regex.Matches(input, pattern)
if matches.Count > 0 then Some [ for m in matches -> m.Value ]
else None
let testString = function
| RegexContains "http://\S+" urls -> printfn "Got urls: %A" urls
| RegexContains "[^@]@[^.]+\.\W+" emails -> printfn "Got email address: %A" emails
| RegexContains "\d+" numbers -> printfn "Got numbers: %A" numbers
| _ -> printfn "Didn't find anything.";;
val ( |RegexContains|_| ) : string -> string -> string list option
val testString : string -> unit
> testString "867-5309, Jenny are you there?";;
Got numbers: ["867"; "5309"]
这等同于编写以下代码
type choice =
| RegexContains of string list
let get_choice pattern input =
let matches = System.Text.RegularExpressions.Regex.Matches(input, pattern)
if matches.Count > 0 then Some (RegexContains [ for m in matches -> m.Value ])
else None
let testString n =
match get_choice "http://\S+" n with
| Some(RegexContains(urls)) -> printfn "Got urls: %A" urls
| None ->
match get_choice "[^@]@[^.]+\.\W+" n with
| Some(RegexContains emails) -> printfn "Got email address: %A" emails
| None ->
match get_choice "\d+" n with
| Some(RegexContains numbers) -> printfn "Got numbers: %A" numbers
| _ -> printfn "Didn't find anything."
使用部分 Active Patterns,我们可以针对任意数量的 Active Patterns 测试输入
let (|StartsWith|_|) needle (haystack : string) = if haystack.StartsWith(needle) then Some() else None
let (|EndsWith|_|) needle (haystack : string) = if haystack.EndsWith(needle) then Some() else None
let (|Equals|_|) x y = if x = y then Some() else None
let (|EqualsMonkey|_|) = function (* "Higher-order" active pattern *)
| Equals "monkey" () -> Some()
| _ -> None
let (|RegexContains|_|) pattern input =
let matches = System.Text.RegularExpressions.Regex.Matches(input, pattern)
if matches.Count > 0 then Some [ for m in matches -> m.Value ]
else None
let testString n =
match n with
| StartsWith "kitty" () -> printfn "starts with 'kitty'"
| StartsWith "bunny" () -> printfn "starts with 'bunny'"
| EndsWith "doggy" () -> printfn "ends with 'doggy'"
| Equals "monkey" () -> printfn "equals 'monkey'"
| EqualsMonkey -> printfn "EqualsMonkey!" (* Note: EqualsMonkey and EqualsMonkey() are equivalent *)
| RegexContains "http://\S+" urls -> printfn "Got urls: %A" urls
| RegexContains "[^@]@[^.]+\.\W+" emails -> printfn "Got email address: %A" emails
| RegexContains "\d+" numbers -> printfn "Got numbers: %A" numbers
| _ -> printfn "Didn't find anything."
部分 Active Patterns 不像传统联合那样将我们限制在有限的案例集上,我们可以在匹配语句中使用任意数量的部分 Active Patterns。
- Active Patterns 简介(Internet Archive Wayback Machine) 作者 Chris Smith
- 通过轻量级语言扩展实现可扩展的模式匹配 作者 Don Syme