Gediscrimineerde vakbonden
Gediscrimineerde unions bieden ondersteuning voor waarden die een van een aantal benoemde gevallen kunnen zijn, mogelijk elk met verschillende waarden en typen. Gediscrimineerde vakbonden zijn nuttig voor heterogene gegevens; gegevens die speciale gevallen kunnen hebben, met inbegrip van geldige en foutgevallen; gegevens die per type variëren van het ene exemplaar naar het andere; en als alternatief voor kleine objecthiërarchieën. Daarnaast worden recursieve gediscrimineerde vakbonden gebruikt om structuurgegevensstructuren te vertegenwoordigen.
Syntaxis
[ attributes ]
type [accessibility-modifier] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
[ member-list ]
Opmerkingen
Gediscrimineerde vakbonden zijn vergelijkbaar met uniontypen in andere talen, maar er zijn verschillen. Net als bij een samenvoegtype in C++ of een varianttype in Visual Basic, worden de gegevens die in de waarde zijn opgeslagen, niet opgelost; het kan een van de verschillende opties zijn. In tegenstelling tot samenvoegingen in deze andere talen krijgt elk van de mogelijke opties echter een case-id. De case-id's zijn namen voor de verschillende mogelijke typen waarden die objecten van dit type kunnen zijn; de waarden zijn optioneel. Als er geen waarden aanwezig zijn, is de case gelijk aan een opsommingscase. Als er waarden aanwezig zijn, kan elke waarde één waarde van een opgegeven type zijn of een tuple waarmee meerdere velden van dezelfde of verschillende typen worden samengevoegd. U kunt een afzonderlijk veld een naam geven, maar de naam is optioneel, zelfs als andere velden in hetzelfde geval een naam hebben.
Toegankelijkheid voor gediscrimineerde vakbonden is standaard ingesteld public
op .
Denk bijvoorbeeld aan de volgende declaratie van een shapetype.
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
De voorgaande code declareert een gediscrimineerde samenvoegshape, die waarden kan hebben van een van de drie gevallen: Rechthoek, Cirkel en Prism. Elke case heeft een andere set velden. De rechthoek heeft twee benoemde velden, beide van het type float
, met de breedte en lengte van de namen. De cirkelcase heeft slechts één benoemd veld, radius. Het prism-geval heeft drie velden, waarvan twee (breedte en hoogte) benoemde velden zijn. Niet-benoemde velden worden anonieme velden genoemd.
U maakt objecten door waarden op te geven voor de benoemde en anonieme velden volgens de volgende voorbeelden.
let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)
Deze code laat zien dat u de benoemde velden in de initialisatie kunt gebruiken of dat u kunt vertrouwen op de volgorde van de velden in de declaratie en alleen de waarden voor elk veld op zijn beurt opgeeft. De constructoroproep voor rect
in de vorige code maakt gebruik van de benoemde velden, maar de constructoroproep voor circ
het gebruik van de volgorde. U kunt de geordende velden en benoemde velden combineren, zoals in de constructie van prism
.
Het option
type is een eenvoudige gediscrimineerde samenvoeging in de F#-kernbibliotheek. Het option
type wordt als volgt gedeclareerd.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
De vorige code geeft aan dat het type Option
een gediscrimineerde samenvoeging is die twee gevallen heeft, Some
en None
. De Some
case heeft een gekoppelde waarde die bestaat uit één anoniem veld waarvan het type wordt vertegenwoordigd door de typeparameter 'a
. De None
case heeft geen gekoppelde waarde. option
Het type geeft dus een algemeen type aan dat een waarde van een bepaald type of geen waarde heeft. Het type Option
heeft ook een alias voor kleine letters, option
die vaker wordt gebruikt.
De case-id's kunnen worden gebruikt als constructors voor het gediscrimineerde samenvoegingstype. De volgende code wordt bijvoorbeeld gebruikt om waarden van het option
type te maken.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
De case-id's worden ook gebruikt in patroonkoppelingsexpressies. In een expressie voor patroonkoppeling worden id's opgegeven voor de waarden die zijn gekoppeld aan de afzonderlijke gevallen. In de volgende code x
is de id bijvoorbeeld de id die is gekoppeld aan het Some
geval van het option
type.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
In patroonkoppelingsexpressies kunt u benoemde velden gebruiken om gediscrimineerde samenvoegingsovereenkomsten op te geven. Voor het shapetype dat eerder is gedeclareerd, kunt u de benoemde velden gebruiken zoals in de volgende code wordt weergegeven om de waarden van de velden op te halen.
let getShapeWidth shape =
match shape with
| Rectangle(width = w) -> w
| Circle(radius = r) -> 2. * r
| Prism(width = w) -> w
Normaal gesproken kunnen de case-id's worden gebruikt zonder deze te kwalificeren met de naam van de samenvoeging. Als u wilt dat de naam altijd wordt gekwalificeerd met de naam van de samenvoeging, kunt u het kenmerk RequireQualifiedAccess toepassen op de definitie van het samenvoegtype.
Gediscrimineerde unies uitpakken
In F# Gediscrimineerde unions worden vaak gebruikt in domeinmodellering voor het verpakken van één type. Het is ook eenvoudig om de onderliggende waarde te extraheren via patroonkoppeling. U hoeft geen overeenkomstexpressie te gebruiken voor één geval:
let ([UnionCaseIdentifier] [values]) = [UnionValue]
In het volgende voorbeeld ziet u dit:
type ShaderProgram = | ShaderProgram of id:int
let someFunctionUsingShaderProgram shaderProgram =
let (ShaderProgram id) = shaderProgram
// Use the unwrapped value
...
Patroonkoppeling is ook rechtstreeks toegestaan in functieparameters, zodat u daar één geval kunt uitpakken:
let someFunctionUsingShaderProgram (ShaderProgram id) =
// Use the unwrapped value
...
Gediscrimineerde vakbonden
U kunt ook gediscrimineerde unions vertegenwoordigen als structs. Dit wordt gedaan met het [<Struct>]
kenmerk.
[<Struct>]
type SingleCase = Case of string
[<Struct>]
type Multicase =
| Case1 of Case1 : string
| Case2 of Case2 : int
| Case3 of Case3 : double
Omdat dit waardetypen zijn en geen verwijzingstypen, zijn er extra overwegingen vergeleken met gediscrimineerde verwijzingen:
- Ze worden gekopieerd als waardetypen en hebben semantiek van het waardetype.
- U kunt geen recursieve typedefinitie gebruiken met een gediscrimineerde samenvoeging met meerdere letters.
- U moet unieke hoofdletters opgeven voor een gediscrimineerde unie met meerdere letters.
Gediscrimineerde unions gebruiken in plaats van objecthiërarchieën
U kunt vaak een gediscrimineerde samenvoeging gebruiken als een eenvoudiger alternatief voor een kleine objecthiërarchie. De volgende gediscrimineerde samenvoeging kan bijvoorbeeld worden gebruikt in plaats van een Shape
basisklasse die afgeleide typen heeft voor cirkel, vierkant, enzovoort.
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
In plaats van een virtuele methode om een gebied of perimeter te berekenen, zoals u zou gebruiken in een objectgeoriënteerde implementatie, kunt u patroonkoppeling gebruiken om te vertakken naar de juiste formules om deze hoeveelheden te berekenen. In het volgende voorbeeld worden verschillende formules gebruikt om het gebied te berekenen, afhankelijk van de 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)
De uitvoer is als volgt:
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
Gediscrimineerde unions gebruiken voor structuurgegevensstructuren
Gediscrimineerde vakbonden kunnen recursief zijn, wat betekent dat de unie zelf kan worden opgenomen in het type van een of meer gevallen. Recursieve gediscrimineerde samenvoegingen kunnen worden gebruikt om structuurstructuren te maken, die worden gebruikt om expressies in programmeertalen te modelleren. In de volgende code wordt een recursieve gediscrimineerde samenvoeging gebruikt voor het maken van een binaire structuurgegevensstructuur. De samenvoeging bestaat uit twee gevallen, Node
een knooppunt met een geheel getal en linker- en rechtersubstructuren, en Tip
, waarmee de structuur wordt beëindigd.
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
In de vorige code resultSumTree
heeft u de waarde 10. In de volgende afbeelding ziet u de structuur voor myTree
.
Gediscrimineerde vakbonden werken goed als de knooppunten in de structuur heterogene zijn. In de volgende code vertegenwoordigt het type Expression
de abstracte syntaxisstructuur van een expressie in een eenvoudige programmeertaal die ondersteuning biedt voor optellen en vermenigvuldigen van getallen en variabelen. Sommige van de samenvoegcases zijn niet recursief en vertegenwoordigen getallen (Number
) of variabelen (Variable
). Andere gevallen zijn recursief en vertegenwoordigen bewerkingen (Add
en Multiply
), waarbij de operanden ook expressies zijn. De Evaluate
functie gebruikt een overeenkomstexpressie om de syntaxisstructuur recursief te verwerken.
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 [ "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
Wanneer deze code wordt uitgevoerd, is de waarde result
5.
Leden
Het is mogelijk om leden van gediscrimineerde vakbonden te definiëren. In het volgende voorbeeld ziet u hoe u een eigenschap definieert en een interface implementeert:
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 -> Math.PI * (r ** 2.0)
| 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{l} and width %f{w}"
Algemene kenmerken
De volgende kenmerken worden vaak gezien in gediscrimineerde vakbonden:
[<RequireQualifiedAccess>]
[<NoEquality>]
[<NoComparison>]
[<Struct>]