Uniones discriminadasDiscriminated Unions

Las uniones discriminadas proporcionan compatibilidad con valores que pueden ser uno de varios casos con nombre, posiblemente cada uno con valores y tipos diferentes.Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types. Las uniones discriminadas son útiles para los datos heterogéneos; datos que pueden tener casos especiales, incluidos casos de error y válidos; datos que varían según el tipo de una instancia a otra; y como alternativa para las jerarquías de objetos pequeños.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. Además, las uniones discriminadas recursivas se utilizan para representar estructuras de datos de árbol.In addition, recursive discriminated unions are used to represent tree data structures.

SintaxisSyntax

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

    [ member-list ]

ComentariosRemarks

Las uniones discriminadas son similares a los tipos de Unión en otros lenguajes, pero hay diferencias.Discriminated unions are similar to union types in other languages, but there are differences. Como con un tipo de Unión C++ en o un tipo variant en Visual Basic, los datos almacenados en el valor no son fijos. puede ser una de las distintas opciones.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. Sin embargo, a diferencia de las uniones en estos otros lenguajes, cada una de las opciones posibles tiene un identificador de caso.Unlike unions in these other languages, however, each of the possible options is given a case identifier. Los identificadores de mayúsculas y minúsculas son nombres para los distintos tipos de valores posibles que podrían ser los objetos de este tipo; los valores son opcionales.The case identifiers are names for the various possible types of values that objects of this type could be; the values are optional. Si los valores no están presentes, el caso es equivalente a un caso de enumeración.If values are not present, the case is equivalent to an enumeration case. Si los valores están presentes, cada valor puede ser un valor único de un tipo especificado o una tupla que agrega varios campos del mismo tipo o de tipos diferentes.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. Puede asignar un nombre a un campo individual, pero el nombre es opcional, incluso si otros campos del mismo caso tienen nombre.You can give an individual field a name, but the name is optional, even if other fields in the same case are named.

La accesibilidad de las uniones discriminadas publictiene como valor predeterminado.Accessibility for discriminated unions defaults to public.

Por ejemplo, considere la siguiente declaración de un tipo de forma.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

El código anterior declara una forma de Unión discriminada, que puede tener valores de cualquiera de los tres casos: Rectángulo, círculo y prisma.The preceding code declares a discriminated union Shape, which can have values of any of three cases: Rectangle, Circle, and Prism. Cada caso tiene un conjunto de campos diferente.Each case has a different set of fields. El caso del rectángulo tiene dos campos con nombre, ambos floatde tipo, que tienen el ancho y la longitud del nombre.The Rectangle case has two named fields, both of type float, that have the names width and length. El caso del círculo tiene solo un campo con nombre, RADIUS.The Circle case has just one named field, radius. El caso de prisma tiene tres campos, dos de los cuales (ancho y alto) son campos con nombre.The Prism case has three fields, two of which (width and height) are named fields. Los campos sin nombre se conocen como campos anónimos.Unnamed fields are referred to as anonymous fields.

Los objetos se crean proporcionando valores para los campos con nombre y anónimos según los ejemplos siguientes.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)

Este código muestra que puede usar los campos con nombre en la inicialización o puede confiar en el orden de los campos en la declaración y simplemente proporcionar los valores de cada campo a su vez.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. La llamada al constructor rect de en el código anterior usa los campos con nombre, pero la llamada circ del constructor para usa la ordenación.The constructor call for rect in the previous code uses the named fields, but the constructor call for circ uses the ordering. Puede mezclar los campos ordenados y los campos con nombre, como en la prismconstrucción de.You can mix the ordered fields and named fields, as in the construction of prism.

El option tipo es una Unión discriminada simple en la F# biblioteca principal.The option type is a simple discriminated union in the F# core library. El option tipo se declara como se indica a continuación.The option type is declared as follows.

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

El código anterior especifica que el tipo Option es una Unión discriminada que tiene dos casos, Some y None.The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. El Some caso tiene un valor asociado que consta de un campo anónimo cuyo tipo se representa mediante el parámetro 'ade tipo.The Some case has an associated value that consists of one anonymous field whose type is represented by the type parameter 'a. El None caso no tiene ningún valor asociado.The None case has no associated value. Por lo option tanto, el tipo especifica un tipo genérico que tiene un valor de algún tipo o ningún valor.Thus the option type specifies a generic type that either has a value of some type or no value. El tipo Option también tiene un alias de tipo en optionminúsculas,, que se usa con más frecuencia.The type Option also has a lowercase type alias, option, that is more commonly used.

Los identificadores de caso se pueden utilizar como constructores para el tipo de Unión discriminada.The case identifiers can be used as constructors for the discriminated union type. Por ejemplo, el código siguiente se utiliza para crear valores del option tipo.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

Los identificadores de caso también se usan en expresiones de coincidencia de patrones.The case identifiers are also used in pattern matching expressions. En una expresión de coincidencia de patrones, se proporcionan identificadores para los valores asociados a los casos individuales.In a pattern matching expression, identifiers are provided for the values associated with the individual cases. Por ejemplo, en el código siguiente, x es el identificador dado el valor que está asociado con el Some caso del option tipo.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."

En expresiones de coincidencia de patrones, puede usar campos con nombre para especificar coincidencias de Unión discriminada.In pattern matching expressions, you can use named fields to specify discriminated union matches. Para el tipo de forma que se declaró anteriormente, puede utilizar los campos con nombre como se muestra en el código siguiente para extraer los valores de los campos.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

Normalmente, los identificadores de caso se pueden usar sin calificarlos con el nombre de la Unión.Normally, the case identifiers can be used without qualifying them with the name of the union. Si desea que el nombre se califique siempre con el nombre de la Unión, puede aplicar el atributo RequireQualifiedAccess a la definición de tipo de Unión.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.

Desencapsular uniones discriminadasUnwrapping Discriminated Unions

En F# las uniones discriminadas, a menudo se usan en el modelado de dominios para ajustar un tipo único.In F# Discriminated Unions are often used in domain-modeling for wrapping a single type. También es fácil extraer el valor subyacente a través de la coincidencia de patrones.It's easy to extract the underlying value via pattern matching as well. No es necesario usar una expresión de coincidencia para un único caso:You don't need to use a match expression for a single case:

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

En el siguiente ejemplo se muestra esto:The following example demonstrates this:

type ShaderProgram = | ShaderProgram of id:int

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

También se permite la coincidencia de patrones directamente en los parámetros de función, por lo que puede desencapsular un solo caso allí: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
    ...

Uniones discriminadas de structStruct Discriminated Unions

También puede representar uniones discriminadas como estructuras.You can also represent Discriminated Unions as structs. Esto se hace con el [<Struct>] atributo.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

Dado que se trata de tipos de valor y no de tipos de referencia, existen consideraciones adicionales en comparación con las uniones discriminadas de referencia:Because these are value types and not reference types, there are extra considerations compared with reference discriminated unions:

  1. Se copian como tipos de valor y tienen semántica de tipos de valor.They are copied as value types and have value type semantics.
  2. No se puede usar una definición de tipo recursiva con una Unión discriminada struct multicase.You cannot use a recursive type definition with a multicase struct Discriminated Union.
  3. Debe proporcionar nombres de casos únicos para una Unión discriminada struct multicase.You must provide unique case names for a multicase struct Discriminated Union.

Usar uniones discriminadas en lugar de jerarquías de objetosUsing Discriminated Unions Instead of Object Hierarchies

A menudo se puede usar una Unión discriminada como alternativa más sencilla a una jerarquía de objetos pequeños.You can often use a discriminated union as a simpler alternative to a small object hierarchy. Por ejemplo, se podría usar la siguiente Unión discriminada en lugar de una Shape clase base que tiene tipos derivados para Circle, Square, etc.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

En lugar de un método virtual para calcular un área o un perímetro, como se usaría en una implementación orientada a objetos, puede usar la coincidencia de patrones para bifurcar a las fórmulas adecuadas para calcular estas cantidades.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. En el ejemplo siguiente, se usan diferentes fórmulas para calcular el área, dependiendo de la forma.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)

La salida es la siguiente: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

Usar uniones discriminadas para estructuras de datos de árbolUsing Discriminated Unions for Tree Data Structures

Las uniones discriminadas pueden ser recursivas, lo que significa que la propia Unión puede incluirse en el tipo de uno o varios casos.Discriminated unions can be recursive, meaning that the union itself can be included in the type of one or more cases. Las uniones discriminadas recursivas se pueden usar para crear estructuras de árbol, que se usan para modelar expresiones en lenguajes de programación.Recursive discriminated unions can be used to create tree structures, which are used to model expressions in programming languages. En el código siguiente, una Unión discriminada recursiva se usa para crear una estructura de datos de árbol binario.In the following code, a recursive discriminated union is used to create a binary tree data structure. La Unión se compone de dos casos Node,, que es un nodo con un valor entero y subárboles izquierdo y derecho, y Tip, que finaliza el árbol.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

En el código anterior, resultSumTree tiene el valor 10.In the previous code, resultSumTree has the value 10. En la ilustración siguiente se muestra la estructura myTreede árbol de.The following illustration shows the tree structure for myTree.

Diagrama que muestra la estructura de árbol de mi árbol.

Las uniones discriminadas funcionan bien si los nodos del árbol son heterogéneos.Discriminated unions work well if the nodes in the tree are heterogeneous. En el código siguiente, el tipo Expression representa el árbol de sintaxis abstracta de una expresión en un lenguaje de programación simple que admite la adición y la multiplicación de números y variables.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. Algunos de los casos de Unión no son recursivos y representan números (Number) o variables (Variable).Some of the union cases are not recursive and represent either numbers (Number) or variables (Variable). Otros casos son recursivos y representan operaciones (Add y Multiply), donde los operandos también son expresiones.Other cases are recursive, and represent operations (Add and Multiply), where the operands are also expressions. La Evaluate función utiliza una expresión de coincidencia para procesar el árbol de sintaxis de forma recursiva.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

Cuando se ejecuta este código, el valor de result es 5.When this code is executed, the value of result is 5.

MiembrosMembers

Es posible definir miembros en uniones discriminadas.It is possible to define members on discriminated unions. En el ejemplo siguiente se muestra cómo definir una propiedad e implementar una interfaz: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

Atributos comunesCommon attributes

Los atributos siguientes se suelen considerar en uniones discriminadas:The following attributes are commonly seen in discriminated unions:

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

Vea tambiénSee also