Размеченные объединения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. Как с типом объединения в C++ или типа variant в Visual Basic, данные, хранящиеся в значении не фиксирована; он может принимать одно из нескольких отдельных вариантов.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. В отличие от объединений в этих других языках, однако каждому из таких возможных вариантов присваивается идентификатор варианта.Unlike unions in these other languages, however, each of the possible options is given a case identifier. Идентификаторы варианта представляют собой имена для различных типов возможных значений, которые могут быть объекты этого типа; значения являются необязательными.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.

Например рассмотрим следующее объявление типа Shape.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

Предыдущий код объявляет фигуры размеченного объединения, которая может иметь значения любого из трех случаях: прямоугольник, круг и 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, которые имеют длину и ширину.The Rectangle case has two named fields, both of type float, that have the names width and length. В случае с кругом имеется только одно именованное поле radius.The Circle case has just one named field, radius. В случае призмы поля содержит 3, два из которых (ширина и высота) с именем поля.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 — это размеченное объединение, имеющее два варианта Some и None.The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. Some Case имеет связанное значение, которое состоит из одного анонимные поля, тип которого представлен параметром типа 'a.The 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.

Идентификаторы варианта может использоваться в качестве конструкторов типа размеченного объединения.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

Идентификаторы варианта также используются в выражениях сопоставления шаблонов.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 идентификатор, которому присваивается значение, с которым связан Some вариант option типа.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

Обычно идентификаторы варианта могут использоваться без указания имени объединения.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 ([UnionCaseName] [values]) = [UnionValue]

Следующий пример демонстрирует это:The following example demonstrates this:

type ShaderProgram = | ShaderProgram of id:int

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

Размеченные объединенияStruct Discriminated Unions

Начиная с F # 4.1, может также представлять размеченные объединения как структуры.Starting with F# 4.1, 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. Ниже показана структура дерева для myTree.The 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). Другие варианты являются рекурсивными и представляют операции (Add и Multiply), в которых операнды также являются выражениями.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.

Общие атрибутыCommon Attributes

Следующие атрибуты часто встречающихся в размеченные объединения:The following attributes are commonly seen in discriminated unions:

  • [RequireQualifiedAccess]
  • [NoEquality]
  • [NoComparison]
  • [Struct]

См. такжеSee also