可区分联合
可区分联合(Discriminated Unions),简称为联合,是一种可以将多个不同意义和结构的数据组合在一起的方法,这点有点和C语言的联合类型相似。同样适用type来定义一个联合类型(F#中自定义类型基本上除元组外都是适用type来定义)。
联合以type关键字开始,然后适用“=” 来定义联合,等好后面使用“|”来定义不同类型的构造器。
构造器通常以大写字母起始的名字开始,构造器需要缩进以避免和标志符混淆。名字后面可以跟可选的数据类型,多个类型之间使用“*”分隔,例如:
type Volume =
| Liter of float
| UsPint of float
| ImperialPint of float
type Shape =
| Square of side:float
| Rectangle of width:float * height:float
| Circle of radius:float
// create an instance of a union type without using the field label
let sq = Square 1.2
// create an instance of a union type using the field label
let sq2 = Square(side=1.2)
// create an instance of a union type using multiple field labels
// and assigning the field out-of-order
let rect3 = Rectangle(height=3.4, width=1.2)
可以使用模式匹配来拆分联合的各个组成部分。当使用模式匹配来匹配一个联合类型时 ,规则的第一部分为联合成员的构造器定义,你不要为每个子类型都定义匹配规则,但如果规则没有包含所有子类型的话,需要定义一个通配规则来匹配余下的子类型。
// type representing volumes
type Volume =
| Liter of float
| UsPint of float
| ImperialPint of float
// various kinds of volumes
let vol1 = Liter 2.5
let vol2 = UsPint 2.5
let vol3 = ImperialPint 2.5
// some functions to convert between volumes
let convertVolumeToLiter x =
match x with
| Liter x -> x
| UsPint x -> x * 0.473
| ImperialPint x -> x * 0.568
let convertVolumeUsPint x =
match x with
| Liter x -> x * 2.113
| UsPint x -> x
| ImperialPint x -> x * 1.201
let convertVolumeImperialPint x =
match x with
| Liter x -> x * 1.760
| UsPint x -> x * 0.833
| ImperialPint x -> x
// a function to print a volume
let printVolumes x =
printfn "Volume in liters = %f,
in us pints = %f,
in imperial pints = %f"
(convertVolumeToLiter x)
(convertVolumeUsPint x)
(convertVolumeImperialPint x)
// print the results
printVolumes vol1
printVolumes vol2
printVolumes vol3
显示结果为
Volume in liters = 2.500000,
in us pints = 5.282500,
in imperial pints = 4.400000
Volume in liters = 1.182500,
in us pints = 2.500000,
in imperial pints = 2.082500
Volume in liters = 1.420000,
in us pints = 3.002500,
in imperial pints = 2.500000
类型定义和类型参数
联合类型和记录类型都支持类型参数化,参数化一个类型意味着暂时不定义联合或记录类型中某些子类型,而是在使用这些类型时在决定哪些定义时未定的子类型。
F#支持两种形式的类型参数化,第一种形式,你把参数化类型放在type关键字和类型名称之间:
type 'a BinaryTree =
| BinaryNode of 'a BinaryTree * 'a BinaryTree
| BinaryValue of 'a
let tree1 =
BinaryNode(
BinaryNode ( BinaryValue 1, BinaryValue 2),
BinaryNode ( BinaryValue 3, BinaryValue 4) )
第二种形式是使用将参数放在尖括号。
type Tree<'a> =
| Node of Tree<'a> list
| Value of 'a
let tree2 =
Node( [ Node( [Value "one"; Value "two"] ) ;
Node( [Value "three"; Value "four"] ) ] )
所有的类型参数都以’开始,然后为字母数字名称,类型参数通常使用单字母来表示,多个类型参数使用逗号分开。
F#类型参数的作用和其它语言比如C#的generic(通用类型或泛型)类似。
在使用包含类型参数的类型时,由于F#编译器强大的类型推断功能,用法和不含类型参数的类型的用法基本上相同,F#会根据上下文推断出类型参数代表的实际的数据类型,比如:
- type 'a BinaryTree =
- | BinaryNode of 'a BinaryTree * 'a BinaryTree
- | BinaryValue of 'a;;
type 'a BinaryTree =
| BinaryNode of 'a BinaryTree * 'a BinaryTree
| BinaryValue of 'a
> let tree1 =
- BinaryNode (
- BinaryNode (BinaryValue 1,BinaryValue 2),
- BinaryNode (BinaryValue 3,BinaryValue 4));;
val tree1 : int BinaryTree =
BinaryNode
(BinaryNode (BinaryValue 1,BinaryValue 2),
BinaryNode (BinaryValue 3,BinaryValue 4))
可以看出F#编译器自动推断出tree1的中’a实际为int类型。
递归定义
前面的BinaryTree 在定义时,引用到了自身,也是一个递归定义,有些时候,在定义一个类型时,需要引用到后面定义的类型,通常情况我们没有办法引用尚未定义过的类型,即使它们定义在同一个文件中。某些情况下,定义在同一个文件中的两个不同类型可能需要相互引用。
F#中定义了一个特别的语法来接近类型的互相引用。两个具有互相引用的类型必须定义同一文件的同一代码块中,而且两个类型定义必须一个紧跟着一个,之间不能有其它类型定义,然后使用”and”关键字连接起来。
比如我们定义一个XML文件
// represents an XML attribute
type XmlAttribute =
{ AttribName: string;
AttribValue: string; }
// represents an XML element
type XmlElement =
{ ElementName: string;
Attributes: list<XmlAttribute>;
InnerXml: XmlTree }
// represents an XML tree
and XmlTree =
| Element of XmlElement
| ElementList of list<XmlTree>
| Text of string
| Comment of string
| Empty