Uniones discriminadas (F#)
Las uniones discriminadas proporcionan compatibilidad con valores que pueden ser uno de los diversos casos con nombre, posiblemente cada uno con valores y tipos diferentes. Las uniones discriminadas son útiles para los datos heterogéneos; datos que pueden tener casos especiales, incluidos casos válidos y casos de error; datos cuyo tipo varía de una instancia a otra; y como alternativa a las jerarquías de objetos de tamaño reducido. Además, las uniones discriminadas se utilizan para representar estructuras de datos en forma de árbol.
type type-name =
| case-identifier1 [of type1 [ * type2 ...]
| case-identifier2 [of type3 [ * type4 ...]
...
Comentarios
Las uniones discriminadas son similares a los tipos de unión de otros lenguajes, pero hay diferencias. Al igual que los tipos de unión en C++ o los tipos variantes en Visual Basic, los datos almacenados en el valor no son datos fijos sino que pueden ser una de varias posibles opciones. A diferencia de las uniones en estos otros lenguajes, se asigna un identificador de caso a cada una de las posibles opciones. Los identificadores de caso son nombres para los diversos posibles tipos de valor que pueden tener los objetos de este tipo; los valores en sí son opcionales. Si los valores no están presentes, el caso equivale a un caso de enumeración. Si los valores están presentes, cada valor puede ser un solo valor del tipo especificado o una tupla que agrega varios valores de los mismos tipos o de tipos diferentes.
El tipo option es una unión discriminada simple de la biblioteca básica de F#. El tipo option se declara de la siguiente manera.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
En el código anterior, se especifica que el tipo Option es una unión discriminada que tiene dos casos, Some y None. El caso Some tiene un valor asociado cuyo tipo viene representado por el parámetro 'a. El caso None no tiene ningún valor asociado. Por consiguiente, option especifica un tipo genérico que tiene un valor de algún tipo o no tiene ningún valor. El tipo Option también tiene un alias en minúsculas, option, que se utiliza con más frecuencia.
Los identificadores de caso se pueden utilizar como constructores del tipo de unión discriminada. Por ejemplo, el siguiente código se utiliza para crear valores del tipo option.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
Los identificadores de caso también se utilizan en las expresiones de coincidencia de modelos. En estas expresiones, se proporcionan identificadores para los valores asociados a los casos individuales. Por ejemplo, en el siguiente código, x es el identificador que se proporciona para el valor asociado al caso Some del tipo option.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
Normalmente, los identificadores de caso se pueden utilizar sin especificar el nombre de la unión. Si desea que se especifique siempre el nombre de la unión, aplique el atributo RequireQualifiedAccess a la definición del tipo de unión.
Usar uniones discriminadas en lugar de jerarquías de objetos
En muchas ocasiones, se puede utilizar una unión discriminada como alternativa más sencilla a una jerarquía de objetos de tamaño reducido. Por ejemplo, se puede usar la siguiente unión discriminada en lugar de una clase base Shape que tiene tipos derivados para el círculo, el cuadrado, etc.
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 usar un método virtual para calcular un área o perímetro, tal y como haría en una implementación orientada a objetos, puede utilizar la coincidencia de modelos a fin de diversificar las fórmulas adecuadas para calcular estas cantidades. En el siguiente ejemplo, según la forma, se usan diferentes fórmulas para calcular el área.
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:
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 las estructuras de datos en árbol
Las uniones discriminadas pueden ser recursivas, lo que significa que la propia unión puede estar incluida en el tipo de uno o varios casos. Las uniones discriminadas recursivas pueden utilizarse para crear estructuras de árbol, que se emplean para modelar las expresiones en los lenguajes de programación. En el siguiente código, se utiliza una unión discriminada recursiva para crear una estructura de datos en forma de árbol binario. La unión consta de dos casos, Node, que es un nodo con un valor entero y subárboles a la izquierda y derecha, y Tip, que termina el árbol.
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. En la siguiente ilustración, se muestra la estructura de árbol de myTree.
Estructura de árbol de myTree
Las uniones discriminadas funcionan bien si los nodos del árbol son heterogéneos. En el siguiente código, el tipo Expression representa el árbol de sintaxis abstracta de una expresión de un lenguaje de programación simple que admite la suma y la multiplicación de números y variables. Algunos de los casos de unión no son recursivos y representan números (Number) o variables (Variable). Otros casos son recursivos y representan operaciones (Add y Multiply), donde los operandos también son expresiones. La función Evaluate utiliza una expresión de coincidencia para procesar el árbol de sintaxis de forma recursiva.
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 ejecute este código, el valor de result será 5.
Vea también
Otros recursos
Historial de cambios
Fecha |
Historial |
Motivo |
---|---|---|
Septiembre de 2010 |
Se corrigió el ejemplo de código. |
Corrección de errores de contenido. |