技术开发 频道

Visual Studio 2010 F#快速上手

  Union类型(Union Types,Discriminated Unions)

  考虑下面的枚举值:

enum CardSuit { Spade = 1, Club = 2, Heart = 3, Diamond = 4};

  理论上,一个card实例只有一种可能的取值,但由于enum本质上只是整数,您不能确定它的值是否是有效的,在C#中,你可以这么写:

CardSuit invalid1 = (CardSuit) 9000;
CardSuit invalid2
= CardSuit.Club | CardSuit.Diamond;

  另外,考虑下面的情形。如果您需要扩展一个enum:

enum Title { Mr, Mrs }

  Title枚举可以工作地很好,但一段时间后,如果需要添加一个“Ms”值,那么每一个switch语句都面临一个潜在的bug。当然您可以尝试修复所有的代码,却难免会发生遗漏。

  枚举可以很好地表达某些概念,但是却无法提供足够的编译器检查。F#中的Union类型可设定为一组有限的值:数据标签(data tag)。例如,考虑一个表示微软员工的Union:

type MicrosoftEmployee =
    
| BillGates
    
| SteveBalmer
    
| Worker of string
    
| Lead of string * MicrosoftEmployee list

  如果有一个MicrosoftEmployee类型的实例,您就知道它必定是{BillGates,SteveBalmer,Worker,Lead}之一。另外,如果它是Worker,您可以知道有一个字符串与之关联,也许是他的名字。我们可以轻松地创建Union类型,而后使用模式匹配(下一小节)来匹配它们的值。

let myBoss = Lead("Yasir", [Worker("Chris"); Worker("Matteo"); Worker("Santosh")])

let printGreeting (emp : MicrosoftEmployee)
=
    match emp with
    
| BillGates   -> printfn "Hello, Bill"
    
| SteveBalmer -> printfn "Hello, Steve"
    
| Worker(name) | Lead(name, _)
                  
-> printfn "Hello, %s" name

  现在假设需要扩展Union类型:

type MicrosoftEmployee =
    
| BillGates
    
| SteveBalmer
    
| Worker of string
    
| Lead   of string * MicrosoftEmployee list
    
| ChrisSmith

  我们会看到一些编译器警告信息:

图1 警告信息

  编译器检测到您没有匹配Union的每一个数据标签,发出了警告。像这样的检查会避免很多bug,要了解

  模式匹配(Pattern Matching)

  模式匹配看起来像是增强版的switch语句,允许您完成分支型控制流程。除了跟常数值进行比较外,还可以捕获新的值。比如在前面的例子中,我们在匹配Union数据标签时绑定了标识符“name”。

let printGreeting (emp : MicrosoftEmployee) =
    match emp with
    
| BillGates   -> printfn "Hello, Bill"
    
| SteveBalmer -> printfn "Hello, Steve"
    
| Worker(name) | Lead(name, _)
                  
-> printfn "Hello, %s" name

  还可以对数据的“结构”进行匹配,比如对列表(list)进行匹配。(还记得吗,x :: y表示x为列表的一个元素,y是x之后的元素,而[]则是空列表。)

let listLength aList =
    match aList with
    
| [] -> 0
    
| a :: [] -> 1
    
| a :: b :: [] -> 2
    
| a :: b :: c :: [] -> 3
    
| _ -> failwith "List is too big!"

  在这个匹配的最后,我们使用了通配符“_”(下划线),它匹配任意值。如果aList变量包含多于三个的元素,最后的模式子句将执行,并抛出一个异常。模式匹配还可以我们执行任意表达式来确定模式是否匹配(如果表达式的值为false,则不匹配)。

let isOdd x =
    match x with
    
| _ when x % 2 = 0 -> false
    
| _ when x % 2 = 1 -> true

  我们甚至可以使用动态类型测试进行匹配:

let getType (x : obj) =
    match x with
    
| :? string -> "x is a string"
    
| :? int -> "x is a int"
    
| :? System.Exception -> "x is an exception"
    
| :? _ -> "invalid type"

  记录类型(Records)

  在声明包含若干个公有属性的类型时,记录类型是一种轻量级的方式。它的一个优势是,借助于类型推演系统,编译器可以通过值的声明得出适当的记录类型。

type Address = {Name : string; Address : string; Zip : int}
let whiteHouse
= {Name = "The White House"; Address = "1600 Pennsylvania Avenue";
        Zip
= 20500}

  在上面的例子中,首先定义了“Address”类型,那么在声明它的实例时,无须显式地使用类型注解,编译器可根据字段(属性)的名称自行得出类型的信息。所以whiteHouse的类型为Address。

0