Размеченные объединения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 ]

RemarksRemarks

Дискриминируемые союзы аналогичны типам профсоюзов на других языках, но существуют различия.Discriminated unions are similar to union types in other languages, but there are differences. Как и в отношении типа соединения в C- или типа варианта в 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.

Например, рассмотрим следующую декларацию типа формы.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. Корпус Rectangle имеет два названных floatполя, оба типа, которые имеют ширину и длину названий.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 тип является дискриминируемым Some союзом, который имеет два случая, и None.The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. Случай Some имеет связанное значение, которое состоит из одного анонимного '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 getShapeWidth shape =
    match shape with
    | Rectangle(width = w) -> w
    | Circle(radius = r) -> 2. * r
    | Prism(width = w) -> w

Как правило, идентификаторы случаев могут быть использованы без квалификации их с именем союза.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. Вам не нужно использовать выражение соответствия для одного случая: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 дискриминируемые союзы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. Вы не можете использовать рекурсивное определение типа с многокомпонентным структурированным дискриминированным союзом.You cannot use a recursive type definition with a multicase struct Discriminated Union.
  3. Вы должны предоставить уникальные имена дел для многокомпонентного структурного дискриминируемого союза.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или переменные ().VariableSome 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 использует выражение соответствия для рекурсивной обработки дерева синтаксиса.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