活动模式活动模式的思想就是让你能把模式匹配语法用于其他数据结构。活动模式允许我们利用.NET类构建类似这样数据结构的联合类型,那么我们就能够匹配这些数据结构。假设我们有一个xml文档,它将被很容易地匹配其中的节点,那么第一步就是利用.NET类型创建我们这个数据结构的联合类型:
let (|Node|Leaf|) (node : #System.Xml.XmlNode) =
if node.HasChildNodes then
Node (node.Name, { for x in node.ChildNodes -> x })
else
Leaf (node.InnerText)
在这里我们看到,我们既定义了一个叶子的模式也定义了节点的模式,如果XmlNode对象具有子节点那么它就是一个节点,否则它就是一个叶子。我们现在能把预先定义的这个叶子和节点模式用于模式匹配,例如如果我们想打印出一个xml文档,可以这样:
let printXml node =
let rec printXml indent node =
match node with
| Leaf (text) -> printfn "%s%s" indent text
| Node (name, nodes) ->
printfn "%s%s:" indent name
nodes |> Seq.iter (printXml (indent +" "))
printXml "" node
在这个例子中如果我们发现是一个叶子那么我们打印出它包含的文本,如果我们发现是一个节点那么我们打印出它的名称并接着继续打印出它的子节点。要使用这个函数,只需初始化一个xml文档并调用我们的打印函数:
let doc =
let temp = new System.Xml.XmlDocument()
let text = "
<fruit>
<apples>
<gannySmiths>1</gannySmiths>
<coxsOrangePippin>3</coxsOrangePippin>
</apples>
<organges>2</organges>
<bananas>4</bananas>
</fruit>"
temp.LoadXml(text)
temp
printXml (doc.DocumentElement :> System.Xml.XmlNode)
我认为就算是这样的简单例子也展现了一种处理xml文档的好方法。如果我们不需要节点类型的太多信息的话,这种方法在很多真实情况下会十分有用。我们可以想象下,一个扩展的xml活动模式函数库,在我们需要关于节点的更多细节时可以处理更多的节点类型。这种方法也能方便地为其他常见的树形结构实现活动模式函数库,例如文件系统:
let (|File| Directory|) (fileSysInfo : System.IO.FileSystemInfo) =
match fileSysInfo with
| :? System.IO.FileInfo as file -> File (file.Name)
| :? System.IO.DirectoryInfo as dir ->
Directory (dir.Name, { for x in dir.GetFileSystemInfos() -> x })
| _ -> assert false
// a System.IO.FileSystemInfo must be either a file or directory
但是活动模式不仅仅用于树形结构。另外一个有用的地方是我们可以在数据上执行不同的检验过程。典型地,我们用户在字符串表单中输入数据时,程序员的一个工作就是把字符串数据转换为某些更有意义和方便处理的数据。一个最容易出问题的情况就是处理时间,因为用于表示时间的格式有很多种。通常我们会对我们录入的时间数据执行多种检测方式,以找到正确的格式,但这些表示为一系列“if then else”语句的检测过程看上去很不整齐和很难维护。现在我们可以用活动模式来生成一个函数库,来解析活动模式并把模式匹配应用到适当的检测过程中去:
open System
let invar = Globalization.CultureInfo.InvariantCulture
let style = Globalization.DateTimeStyles.None
let (|ParseIsoDate|_|) str =
let res,date = DateTime.TryParseExact(str, "yyyy-MM-dd", invar, style)
if res then Some date else None
let (|ParseAmericanDate|_|) str =
let res,date = DateTime.TryParseExact(str, "MM-dd-yyyy", invar, style)
if res then Some date else None
let (|Parse3LetterMonthDate|_|) str =
let res,date = DateTime.TryParseExact(str, "MMM-dd-yyyy", invar, style)
if res then Some date else None
这里,我们定义了3个不同的活动模式来解析时间,ParseIsoDate、ParseAmericanDate和Parse3LetterMonthDate。我们在模式末尾使用了一个下划线来表示这个模式是非完整的,即模式要不找到一个时间数据或者不能。这不像之前的例子中,我们能断言一个模式的执行结果,对于xml节点来说不是节点就是叶子,我们也不允许有其他可能的情况存在。实际上,除为了避免编译警告我们必须为模式提供一个默认值之外,使用非完整模式和使用完整模式没有很大的不同;同时我们还可以在一次检测过程中提供多个非完整模式,只要他们都能处理同种类型的录入数据。我们通过下面的例子来描述如何使用这3个时间活动模式来将一个字符串解析成时间:
let parseDate str =
match str with
| ParseIsoDate d -> d
| ParseAmericanDate d -> d
| Parse3LetterMonthDate d -> d
| _ -> failwith "unrecognized date format"
parseDate "05-23-1978"
parseDate "May-23-1978"
parseDate "1978-05-23"
parseDate "05-23-78"
我们的例子成功解析了前3个时间,但对于最后一个使用2位数字来表示年的时间字符串,由于我们没有提供对应的模式,所以它没有被成功解析。提供一个时间模式的函数库的这种方式,能让我们处理这样及其他很多格式的时间,并提供给程序员一个快速明了的方式来表述哪些时间格式是被允许的。最后,部分活动模式通过参数化处理后,可以让模式更好地重用。下面我们演示一个正则表达式活动模式的例子。它是参数化的,以便我们能获得一个可以处理任何我们想要的正则表达式:
let (|ParseRegex|_|) re s =
let re = new System.Text.RegularExpressions.Regex(re)
let matches = re.Matches(s)
if matches.Count > 0 then
Some { for x in matches -> x.Value }
else
None
let parse s =
match s with
| ParseRegex "\d+" results -> printfn "Digits: %A" results
| ParseRegex "\w+" results -> printfn "Ids: %A" results
| ParseRegex "\s+" results -> printfn "Whitespace: %A" results
| _ -> failwith "known type"
parse "hello world"
parse "42 7 8"
parse "\t\t\t"
当编译并执行这个例子,会显示:
Ids: seq ["hello"; "world"]
Digits: seq ["42"; "7"; "8"]
Whitespace: seq ["\t\t\t"]具有解析器实践经验的读者可能会注意到,这和由“lex”风格的编程工具生成的标记器是很相似的。其实,这个例子的行为和一个lex风格的标记器行为有着几个关键的不同点;在这里,整个字符串被用于所有匹配的搜索,而一个lex风格的标记器会从字符串的开始位置执行很长的匹配。然而,我相信如果一个人需要构建一个标记器并想避免由于使用其他编程工具所带来的复杂性的话,那么他可以构建一个活动模式来满足这样的需求。
总结这篇文章是F#中模式匹配的一个快速浏览,并介绍了它的新活动模式的特性。我们看到了模式匹配为什么是重要的,它帮助我们构建更清晰更容易维护的代码,并看到了这个思想是如何被活动模式进行扩展的。如果你有兴趣学习更多关于活动模式的知识,可以看看Don Syme写的这些博客文章,其中包括了一个论文的连接,这个论文提供了关于活动模式设计的更多细节。
F#资源在网络上有大量不断增加的F#资源,下面是一个最佳资源的小总结:
走向何方?经过在去年添加了一些特性到F#语言和扩展函数库后,这个语言已经进入一个新的阶段。虽然F#的实现已经具有很高的质量,但微软团队似乎愈来愈有兴趣为F#提供更多的官方支持。F#作为.NET语言生态系统的一个增值工具,似乎更具有一个光明的前途。另外,F#团队打算在明年优化编译器,以让它更优良,并提高函数库、工具和文档的质量。