F#
Uniones discriminadas
Buscar..
Nombrando elementos de tuplas dentro de uniones discriminadas
Al definir uniones discriminadas, puede nombrar elementos de tipos de tuplas y usar estos nombres durante la comparación de patrones.
type Shape =
| Circle of diameter:int
| Rectangle of width:int * height:int
let shapeIsTenWide = function
| Circle(diameter=10)
| Rectangle(width=10) -> true
| _ -> false
Además, nombrar los elementos de uniones discriminadas mejora la legibilidad del código y la interoperabilidad con C #; los nombres proporcionados se utilizarán para los nombres de las propiedades y los parámetros de los constructores. Los nombres generados predeterminados en el código de interoperabilidad son "Elemento", "Elemento1", "Elemento2" ...
Uso Básico Discriminado de la Unión
Las uniones discriminadas en F # ofrecen una forma de definir tipos que pueden contener cualquier número de tipos de datos diferentes. Su funcionalidad es similar a las uniones C ++ o variantes de VB, pero con el beneficio adicional de ser de tipo seguro.
// define a discriminated union that can hold either a float or a string
type numOrString =
| F of float
| S of string
let str = S "hi" // use the S constructor to create a string
let fl = F 3.5 // use the F constructor to create a float
// you can use pattern matching to deconstruct each type
let whatType x =
match x with
| F f -> printfn "%f is a float" f
| S s -> printfn "%s is a string" s
whatType str // hi is a string
whatType fl // 3.500000 is a float
Uniones al estilo Enum
La información de tipo no necesita ser incluida en los casos de una unión discriminada. Al omitir la información de tipo, puede crear una unión que simplemente representa un conjunto de opciones, similar a una enumeración.
// This union can represent any one day of the week but none of
// them are tied to a specific underlying F# type
type DayOfWeek = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
Convertir hacia y desde cuerdas con Reflexión
A veces es necesario convertir una Unión Discriminada hacia y desde una cadena:
module UnionConversion
open Microsoft.FSharp.Reflection
let toString (x: 'a) =
match FSharpValue.GetUnionFields(x, typeof<'a>) with
| case, _ -> case.Name
let fromString<'a> (s : string) =
match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
| [|case|] -> Some(FSharpValue.MakeUnion(case, [||])) :?> 'a)
| _ -> None
Unión individual discriminada caso
Una unión discriminada de un solo caso es como cualquier otra unión discriminada, excepto que solo tiene un caso.
// Define single-case discriminated union type.
type OrderId = OrderId of int
// Construct OrderId type.
let order = OrderId 123
// Deconstruct using pattern matching.
// Parentheses used so compiler doesn't think it is a function definition.
let (OrderId id) = order
Es útil para hacer cumplir la seguridad de tipos y se usa comúnmente en F # en lugar de C # y Java, donde la creación de nuevos tipos conlleva más sobrecarga.
Las siguientes dos definiciones de tipos alternativos dan como resultado que se declare la misma unión discriminada de un solo caso:
type OrderId = | OrderId of int
type OrderId =
| OrderId of int
Uso de uniones discriminadas de un solo caso como registros
A veces es útil crear tipos de unión con un solo caso para implementar tipos similares a registros:
type Point = Point of float * float
let point1 = Point(0.0, 3.0)
let point2 = Point(-2.5, -4.0)
Se vuelven muy útiles porque pueden descomponerse mediante la coincidencia de patrones de la misma manera que los argumentos de la tupla pueden:
let (Point(x1, y1)) = point1
// val x1 : float = 0.0
// val y1 : float = 3.0
let distance (Point(x1,y1)) (Point(x2,y2)) =
pown (x2-x1) 2 + pown (y2-y1) 2 |> sqrt
// val distance : Point -> Point -> float
distance point1 point2
// val it : float = 7.433034374
RequireQualifiedAccess
Con el atributo RequireQualifiedAccess
, los casos de unión deben denominarse MyUnion.MyCase
lugar de solo MyCase
. Esto evita las colisiones de nombres en el espacio de nombres o el módulo adjunto:
type [<RequireQualifiedAccess>] Requirements =
None | Single | All
// Uses the DU with qualified access
let noRequirements = Requirements.None
// Here, None still refers to the standard F# option case
let getNothing () = None
// Compiler error unless All has been defined elsewhere
let invalid = All
Si, por ejemplo, el System
se ha abierto, Single
refiere a System.Single
. No hay colisión con los Requirements.Single
caso del sindicato. Solo.
Uniones recursivas discriminadas
Tipo recursivo
Las uniones discriminadas pueden ser recursivas, es decir, pueden referirse a sí mismas en su definición. El primer ejemplo aquí es un árbol:
type Tree =
| Branch of int * Tree list
| Leaf of int
Como ejemplo, definamos el siguiente árbol:
1
2 5
3 4
Podemos definir este árbol usando nuestra unión recursiva discriminada de la siguiente manera:
let leaf1 = Leaf 3
let leaf2 = Leaf 4
let leaf3 = Leaf 5
let branch1 = Branch (2, [leaf1; leaf2])
let tip = Branch (1, [branch1; leaf3])
Iterar sobre el árbol es solo una cuestión de coincidencia de patrones:
let rec toList tree =
match tree with
| Leaf x -> [x]
| Branch (x, xs) -> x :: (List.collect toList xs)
let treeAsList = toList tip // [1; 2; 3; 4; 5]
Tipos recursivos mutuamente dependientes
Una forma de lograr la recursión es tener tipos anidados mutuamente dependientes.
// BAD
type Arithmetic = {left: Expression; op:string; right: Expression}
// illegal because until this point, Expression is undefined
type Expression =
| LiteralExpr of obj
| ArithmeticExpr of Arithmetic
La definición de un tipo de registro directamente dentro de una unión discriminada está en desuso:
// BAD
type Expression =
| LiteralExpr of obj
| ArithmeticExpr of {left: Expression; op:string; right: Expression}
// illegal in recent F# versions
Puede usar la palabra clave and
para encadenar definiciones mutuamente dependientes:
// GOOD
type Arithmetic = {left: Expression; op:string; right: Expression}
and Expression =
| LiteralExpr of obj
| ArithmeticExpr of Arithmetic