Uniões discriminadas (F#)
Discriminated uniões oferecem suporte para valores que podem ser um dos vários casos nomeados, possivelmente, cada um com valores diferentes e tipos.Uniões discriminadas são úteis para dados heterogêneos; dados que podem ter casos especiais, incluindo válido e casos de erro. dados que variam em tipo de uma instância para outra; e como uma alternativa para hierarquias de objetos pequenos.Além disso, recursiva discriminated uniões são usados para representar estruturas de dados de árvore.
type type-name =
| case-identifier1 [of type1 [ * type2 ...]
| case-identifier2 [of type3 [ * type4 ...]
...
Comentários
Uniões discriminadas são semelhantes aos tipos de união em outros idiomas, mas há diferenças.Como com um tipo de união em C++ ou de um tipo variant em Visual Basic, os dados armazenados no valor não é fixo; ele pode ser uma das várias opções distintas.Ao contrário de uniões nesses outros idiomas, no entanto, cada uma das opções possíveis é dado um identificador de análise.Os identificadores de maiúsculas são nomes para os vários tipos possíveis de valores que podem ser objetos desse tipo; os valores são opcionais.Se os valores não estiverem presentes, o caso é equivalente a um caso de enumeração.Se os valores estiverem presentes, cada valor pode ser um único valor de um tipo especificado, ou uma tupla que agrega os valores de vários dos tipos iguais ou diferentes.
O option o tipo é uma simple união discriminada na biblioteca do núcleo F#.O option tipo é declarado da seguinte maneira.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
O código anterior Especifica que o tipo de Option é uma união discriminada que tem dois casos, Some e None.O Some caso tenha um valor associado cujo tipo é representado por um parâmetro de tipo 'a.O None caso não tem valor associado.Assim, o option type Especifica um tipo genérico que tem um valor de algum tipo ou nenhum valor.O tipo de Option também tem um alias de tipo de letra minúscula, option, isto é mais comumente usados.
Os identificadores de ocorrências podem ser usados como construtores para o tipo de união discriminado.Por exemplo, o código a seguir é usado para criar valores da option tipo.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
Os identificadores de maiúsculas também são usados em expressões de correspondência de padrão.Em um expressão de correspondência de padrão, os identificadores são fornecidos para os valores associados os casos individuais.Por exemplo, no código a seguir, x o identificador recebe o valor que está associado a Some caso do option tipo.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
Normalmente, os identificadores de ocorrências podem ser usados sem qualificação-los com o nome da união.Se desejar que o nome para sempre ser qualificados com o nome da união, você pode aplicar o RequireQualifiedAccess de atributo para a definição de tipo de união.
Usando o discriminada uniões em vez de hierarquias de objeto
Normalmente, você pode usar uma união discriminada como uma alternativa mais simples para uma hierarquia de objetos pequenos.Por exemplo, união discriminada seguinte poderia ser usada em vez de um Shape basear a classe que tem derivada tipos para o círculo, quadrado, e assim por diante.
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
Em vez disso, de um método virtual para calcular uma área ou perímetro, como você usaria em uma implementação orientada a objeto, você pode usar a correspondência de padrões para a ramificação apropriada fórmulas para calcular essas quantidades.No exemplo a seguir, as fórmulas diferentes são usadas para calcular a área, dependendo da forma.
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)
A saída é o seguinte:
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
Usando as uniões discriminadas para estruturas de dados de árvore
Uniões discriminadas podem ser recursivas, o que significa que a própria união pode ser incluída no tipo de um ou mais casos.Recursiva discriminadas uniões podem ser usados para criar estruturas de árvore, que são usados para expressões de modelo em linguagens de programação.No código a seguir, uma recursiva discriminated união é usada para criar uma estrutura de dados de árvore binária.A união consiste em dois casos, Node, que é um nó com um valor inteiro e subárvores esquerdas e direita, e Tip, que termina a árvore.
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
No código anterior, resultSumTree tem o valor 10.A ilustração a seguir mostra a estrutura de árvore para myTree.
Estrutura de árvore para myTree
Uniões discriminadas funcionam bem se os nós da árvore são heterogêneos.No código a seguir, o tipo de Expression representa a árvore de sintaxe abstrata de uma expressão em uma linguagem de programação simple, suporte à adição e multiplicação de números e variáveis.Alguns dos casos de união não são recursivos e representam um dos números (Number) ou variáveis (Variable).Outros casos não são recursivos e representam as operações (Add e Multiply), onde os operandos também são expressões.O Evaluate função usa uma expressão de correspondência para o processo de recursivamente a árvore de sintaxe.
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
Quando esse código é executado, o valor do result é 5.