可区分联合 (F#)
可区分联合支持具有以下特点的值:它可能属于多个命名用例中的一个,而每个命名用例的值和类型可能各不相同。 可区分联合对于以下数据很有用:异类数据;可有特殊用例(包括有效用例和错误用例)的数据;每个实例的类型不同的数据;可区分联合还可用作小对象层次结构的备用项。 此外,递归的可区分联合用于表示树数据结构。
type type-name =
| case-identifier1 [of type1 [ * type2 ...]
| case-identifier2 [of type3 [ * type4 ...]
...
备注
可区分联合与其他语言的联合类型相似,但也存在不同。 与 C++ 中的联合类型或 Visual Basic 中的 Variant 类型一样,存储在值中的数据不是固定的;它可以是若干个不同选项中的一个。 与这些其他语言中的联合不同,将为每个可能的选项提供一个用例标识符。 用例标识符是此类型的对象可能属于的各种值类型的名称;值是可选的。 如果值不存在,则此用例等效于枚举用例。 如果值存在,则每个值既可以是指定类型的单个值,也可以是聚合相同类型或不同类型的多个值的元组。
option 类型是 F# 核心库中的一个简单的可区分联合。 option 类型按如下方式声明。
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
前面的代码指定 Option 类型是具有两个用例(Some 和 None)的可区分联合。 Some 用例具有一个关联的值,其类型是由 'a 类型参数表示。 None 用例没有关联的值。 因此,option 类型指定一个既可以具有某类型的值也可以没有任何值的泛型类型。 Option 类型还具有更常用的小写的类型别名 option。
用例标识符可用作可区分联合类型的构造函数。 例如,下面的代码用于创建 option 类型的值。
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
用例标识符还用在模式匹配表达式中。 在模式匹配表达式中,将为与各个用例关联的值提供标识符。 例如,在下面的代码中,x 是一个标识符,将为其指定与 option 类型的 Some 用例关联的值。
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
通常,在使用用例标识符时无需用联合名称对其进行限定。 如果需要始终用联合名称来限定用例标识符名称,则可将 RequireQualifiedAccess 特性应用于联合类型定义。
使用可区分联合替代对象层次结构
通常可以将可区分联合用作小对象层次结构的更简单的替代项。 例如,下面的可区分联合可用于替代具有圆形、方形等的派生类型的 Shape 基类。
type Shape =
// The value here is the radius.
| Circle of float
// The value here is the side length.
| EquilateralTriangle of double
// The value here is the side length.
| Square of double
// The values here are the height and width.
| Rectangle of double * double
可以使用模式匹配分支到合适的公式来计算面积或周长,而不必像在面向对象的实现中那样使用虚方法来计算这些数量。 在下面的示例中,将根据形状使用不同的公式来计算面积。
let pi = 3.141592654
let area myShape =
match myShape with
| Circle radius -> pi * radius * radius
| EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
| Square s -> s * s
| Rectangle (h, w) -> h * w
let radius = 15.0
let myCircle = Circle(radius)
printfn "Area of circle that has radius %f: %f" radius (area myCircle)
let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)
let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)
输出如下所示:
Area of circle that has radius 15.000000: 706.858347
Area of square that has side 10.000000: 100.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000
对树数据结构使用可区分联合
可区分联合可以是递归的,这意味着联合本身也包括在一个或多个用例的类型中。 递归的可区分联合可用于创建可在编程语言中用来构建表达式模型的树结构。 在下面的代码中,递归的可区分联合用于创建二进制树数据结构。 该联合包括 Node 和 Tip 两个用例,前者是一个包含整数值和左、右子树的节点,后者用于终结树。
type Tree =
| Tip
| Node of int * Tree * Tree
let rec sumTree tree =
match tree with
| Tip -> 0
| Node(value, left, right) ->
value + sumTree(left) + sumTree(right)
let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip))
let resultSumTree = sumTree myTree
在前面的代码中,resultSumTree 具有值 10。 下面的插图显示了 myTree 的树结构。
myTree 的树结构
如果树中的节点是异类的,则可区分联合可以很好地工作。 在下面的代码中,类型 Expression 表示用简单的编程语言(该编程语言支持数字和变量的加法和乘法)编写的表达式的抽象语法树。 一些联合用例不是递归的,表示数字 (Number) 或变量 (Variable)。 其他用例是递归的,表示操作(Add 和 Multiply),其中的操作数也是表达式。 Evaluate 函数使用匹配表达式以递归方式处理语法树。
type Expression =
| Number of int
| Add of Expression * Expression
| Multiply of Expression * Expression
| Variable of string
let rec Evaluate (env:Map<string,int>) exp =
match exp with
| Number n -> n
| Add (x, y) -> Evaluate env x + Evaluate env y
| Multiply (x, y) -> Evaluate env x * Evaluate env y
| Variable id -> env.[id]
let environment = Map.ofList [ "a", 1 ;
"b", 2 ;
"c", 3 ]
// Create an expression tree that represents
// the expression: a + 2 * b.
let expressionTree1 = Add(Variable "a", Multiply(Number 2, Variable "b"))
// Evaluate the expression a + 2 * b, given the
// table of values for the variables.
let result = Evaluate environment expressionTree1
当执行此代码时,result 的值为 5。