可区分联合Discriminated Unions

可区分联合提供的值支持可以是多个命名用例中的一个,可能每个值都有不同的值和类型。Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types. 可区分联合适用于异类数据;可能有特殊情况的数据,包括有效和错误事例;不同类型的数据的不同之处在于:作为小型对象层次结构的替代方法。Discriminated unions are useful for heterogeneous data; data that can have special cases, including valid and error cases; data that varies in type from one instance to another; and as an alternative for small object hierarchies. 此外,递归的可区分联合用于表示树数据结构。In addition, recursive discriminated unions are used to represent tree data structures.

语法Syntax

[ attributes ]
type [accessibility-modifier] type-name =
    | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
    | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]

    [ member-list ]

备注Remarks

可区分联合类似于其他语言中的联合类型,但有一些差异。Discriminated unions are similar to union types in other languages, but there are differences. 与 Visual Basic 中的联合类型C++或变体类型一样,值中存储的数据不是固定的;它可以是几个不同的选项之一。As with a union type in C++ or a variant type in Visual Basic, the data stored in the value is not fixed; it can be one of several distinct options. 与其他这些语言中的联合不同,每个可能的选项都有一个case 标识符Unlike unions in these other languages, however, each of the possible options is given a case identifier. Case 标识符是此类型的对象可能具有的各种类型的值的名称;值是可选的。The case identifiers are names for the various possible types of values that objects of this type could be; the values are optional. 如果值不存在,则该事例等效于枚举用例。If values are not present, the case is equivalent to an enumeration case. 如果存在值,则每个值可以是指定类型的单个值,也可以是聚合同一类型或不同类型的多个字段的元组。If values are present, each value can either be a single value of a specified type, or a tuple that aggregates multiple fields of the same or different types. 您可以为单个字段指定一个名称,但该名称是可选的,即使在同一种情况下,也是如此。You can give an individual field a name, but the name is optional, even if other fields in the same case are named.

可区分的联合的辅助public功能默认为。Accessibility for discriminated unions defaults to public.

例如,请考虑下面的形状类型声明。For example, consider the following declaration of a Shape type.

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

前面的代码声明了一个可区分的联合形状,其中的值可以是以下三种情况之一:Rectangle、Circle 和 Prism。The preceding code declares a discriminated union Shape, which can have values of any of three cases: Rectangle, Circle, and Prism. 每个事例都有一组不同的字段。Each case has a different set of fields. 矩形用例有两个命名字段,两个float均为类型,名称为 width 和 length。The Rectangle case has two named fields, both of type float, that have the names width and length. 圆形大小写只包含一个命名字段 "半径"。The Circle case has just one named field, radius. Prism 用例有三个字段,其中两个字段(宽度和高度)为 "命名字段"。The Prism case has three fields, two of which (width and height) are named fields. 未命名字段称为匿名字段。Unnamed fields are referred to as anonymous fields.

您可以根据以下示例,为已命名和匿名字段提供值来构造对象。You construct objects by providing values for the named and anonymous fields according to the following examples.

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

此代码显示,您可以在初始化中使用已命名的字段,也可以依赖于声明中的字段顺序,而只是提供每个字段的值。This code shows that you can either use the named fields in the initialization, or you can rely on the ordering of the fields in the declaration and just provide the values for each field in turn. 前面代码rect中的构造函数调用使用命名字段,但的构造函数circ调用使用排序。The constructor call for rect in the previous code uses the named fields, but the constructor call for circ uses the ordering. 可以混合使用已排序的字段和命名字段,如的构造prism中所示。You can mix the ordered fields and named fields, as in the construction of prism.

option类型是F#核心库中的一个简单的可区分联合。The option type is a simple discriminated union in the F# core library. option类型声明如下。The option type is declared as follows.

// The option type is a discriminated union.
type Option<'a> =
    | Some of 'a
    | None

上面的代码指定该类型Option是具有两个用例(和NoneSome的可区分联合。The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. 事例具有一个关联的值,它由一个匿名字段组成,该字段的类型由类型'a参数表示。 SomeThe Some case has an associated value that consists of one anonymous field whose type is represented by the type parameter 'a. None事例没有关联值。The None case has no associated value. 因此, option类型指定泛型类型,该类型具有某种类型的值或没有值。Thus the option type specifies a generic type that either has a value of some type or no value. 该类型Option还具有一个更常用的小写option类型别名。The type Option also has a lowercase type alias, option, that is more commonly used.

Case 标识符可用作可区分联合类型的构造函数。The case identifiers can be used as constructors for the discriminated union type. 例如,下面的代码用于创建option类型的值。For example, the following code is used to create values of the option type.

let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None

Case 标识符还用于模式匹配表达式。The case identifiers are also used in pattern matching expressions. 在模式匹配表达式中,会为与单个用例关联的值提供标识符。In a pattern matching expression, identifiers are provided for the values associated with the individual cases. 例如,在下面的代码中, x是给定与option类型的Some大小写关联的值的标识符。For example, in the following code, x is the identifier given the value that is associated with the Some case of the option type.

let printValue opt =
    match opt with
    | Some x -> printfn "%A" x
    | None -> printfn "No value."

在模式匹配表达式中,可以使用命名字段来指定可区分的联合匹配。In pattern matching expressions, you can use named fields to specify discriminated union matches. 对于之前声明的形状类型,可以使用命名字段,如以下代码所示,以便提取字段的值。For the Shape type that was declared previously, you can use the named fields as the following code shows to extract the values of the fields.

let getShapeHeight shape =
    match shape with
    | Rectangle(height = h) -> h
    | Circle(radius = r) -> 2. * r
    | Prism(height = h) -> h

通常情况下,可以使用 case 标识符,无需使用联合名称来限定它们。Normally, the case identifiers can be used without qualifying them with the name of the union. 如果希望名称始终使用联合的名称进行限定,则可将RequireQualifiedAccess特性应用于联合类型定义。If you want the name to always be qualified with the name of the union, you can apply the RequireQualifiedAccess attribute to the union type definition.

解包可区分联合Unwrapping Discriminated Unions

在F#可区分联合中,通常用于包装单个类型的域建模。In F# Discriminated Unions are often used in domain-modeling for wrapping a single type. 也可以通过模式匹配轻松提取基础值。It's easy to extract the underlying value via pattern matching as well. 对于一个事例,无需使用 match 表达式:You don't need to use a match expression for a single case:

let ([UnionCaseIdentifier] [values]) = [UnionValue]

下面的示例演示这一操作:The following example demonstrates this:

type ShaderProgram = | ShaderProgram of id:int

let someFunctionUsingShaderProgram shaderProgram =
    let (ShaderProgram id) = shaderProgram
    // Use the unwrapped value
    ...

函数参数中也可以直接使用模式匹配,因此,可以将一个事例解包到其中:Pattern matching is also allowed directly in function parameters, so you can unwrap a single case there:

let someFunctionUsingShaderProgram (ShaderProgram id) =
    // Use the unwrapped value
    ...

结构可区分联合Struct Discriminated Unions

您还可以将可区分联合表示为结构。You can also represent Discriminated Unions as structs. 此操作是通过[<Struct>]属性实现的。This is done with the [<Struct>] attribute.

[<Struct>]
type SingleCase = Case of string

[<Struct>]
type Multicase =
    | Case1 of Case1 : string
    | Case2 of Case2 : int
    | Case3 of Case3 : double

由于这些是值类型而不是引用类型,因此与引用可区分联合相比,有一些额外的注意事项:Because these are value types and not reference types, there are extra considerations compared with reference discriminated unions:

  1. 它们被复制为值类型并具有值类型语义。They are copied as value types and have value type semantics.
  2. 不能将递归类型定义用于 multicase 结构可区分联合。You cannot use a recursive type definition with a multicase struct Discriminated Union.
  3. 必须为 multicase 结构可区分联合提供唯一的事例名称。You must provide unique case names for a multicase struct Discriminated Union.

使用可区分联合而不是对象层次结构Using Discriminated Unions Instead of Object Hierarchies

通常,可以将可区分联合用作小型对象层次结构的更简单的替代方法。You can often use a discriminated union as a simpler alternative to a small object hierarchy. 例如,可以使用以下可区分联合,而不Shape是具有用于圆形、方形等的派生类型的基类。For example, the following discriminated union could be used instead of a Shape base class that has derived types for circle, square, and so on.

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

与在面向对象的实现中使用的一样,可以使用模式匹配分支到适当的公式来计算这些数量,而不是使用虚方法来计算区域或外围网络。Instead of a virtual method to compute an area or perimeter, as you would use in an object-oriented implementation, you can use pattern matching to branch to appropriate formulas to compute these quantities. 在下面的示例中,使用不同的公式来计算区域,具体取决于形状。In the following example, different formulas are used to compute the area, depending on the shape.

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)

输出如下所示:The output is as follows:

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

对树形数据结构使用可区分联合Using Discriminated Unions for Tree Data Structures

可区分联合可以是递归的,这意味着可将联合本身包含在一个或多个事例的类型中。Discriminated unions can be recursive, meaning that the union itself can be included in the type of one or more cases. 递归的可区分联合可用于创建树结构,用于在编程语言中为表达式建模。Recursive discriminated unions can be used to create tree structures, which are used to model expressions in programming languages. 在下面的代码中,递归的可区分联合用于创建二进制树数据结构。In the following code, a recursive discriminated union is used to create a binary tree data structure. 联合包括两个事例, Node即一个具有整数值的节点、左右子树和Tip,后者终止树。The union consists of two cases, Node, which is a node with an integer value and left and right subtrees, and Tip, which terminates the tree.

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。In the previous code, resultSumTree has the value 10. 下图显示了的树结构myTreeThe following illustration shows the tree structure for myTree.

显示 myTree 的树结构的关系图。

如果树中的节点是异类的,则可区分的联合工作良好。Discriminated unions work well if the nodes in the tree are heterogeneous. 在下面的代码中,类型Expression表示简单编程语言中表达式的抽象语法树,它支持数字和变量的加法和乘法。In the following code, the type Expression represents the abstract syntax tree of an expression in a simple programming language that supports addition and multiplication of numbers and variables. 某些联合用例不是递归的,表示数字(Number)或变量(Variable)。Some of the union cases are not recursive and represent either numbers (Number) or variables (Variable). 其他情况是递归的,表示运算(AddMultiply),其中操作数也是表达式。Other cases are recursive, and represent operations (Add and Multiply), where the operands are also expressions. Evaluate函数使用 match 表达式以递归方式处理语法树。The Evaluate function uses a match expression to recursively process the syntax tree.

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。When this code is executed, the value of result is 5.

成员Members

可以在可区分的联合上定义成员。It is possible to define members on discriminated unions. 下面的示例演示如何定义属性和实现接口:The following example shows how to define a property and implement an interface:

open System

type IPrintable =
    abstract Print: unit -> unit

type Shape =
    | Circle of float
    | EquilateralTriangle of float
    | Square of float
    | Rectangle of float * float

    member this.Area =
        match this with
        | Circle r -> 2.0 * Math.PI * r
        | EquilateralTriangle s -> s * s * sqrt 3.0 / 4.0
        | Square s -> s * s
        | Rectangle(l, w) -> l * w

    interface IPrintable with
        member this.Print () =
            match this with
            | Circle r -> printfn "Circle with radius %f" r
            | EquilateralTriangle s -> printfn "Equilateral Triangle of side %f" s
            | Square s -> printfn "Square with side %f" s
            | Rectangle(l, w) -> printfn "Rectangle with length %f and width %f" l w

常见属性Common attributes

以下属性通常出现在可区分联合中:The following attributes are commonly seen in discriminated unions:

  • [<RequireQualifiedAccess>]
  • [<NoEquality>]
  • [<NoComparison>]
  • [<Struct>]

请参阅See also