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 unies gebruikt om boomstructuren 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. Zoals bij een union type in C++ of een varianttype in Visual Basic, zijn de gegevens in de opgeslagen waarde niet vast; het kan een van de meerdere 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 benadeelde vakbonden is standaard public
.
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 verklaart een gediscrimineerde vereniging Shape, die waarden kan hebben van een van de drie gevallen: Rechthoek, Cirkel en Prisma. Elke case heeft een andere set velden. Rechthoek heeft twee velden met naam, beide van het type float
, die de namen breedte en lengte hebben. 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 constructor-aanroep voor circ
gebruikt 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 met twee gevallen, 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. Het option
type geeft dus een algemeen type op 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 is x
de identificator die bijvoorbeeld de waarde heeft die is gegeven 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 unie overeenkomsten 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 kunnen de zaak-id's worden gebruikt zonder ze te specificeren met de naam van de unie. Als u wilt dat de naam altijd wordt gekwalificeerd met de unienaam, kunt u het RequireQualifiedAccess attribuut toepassen op de definitie van het unietype.
Gediscrimineerde unies uitpakken
In F# worden gediscrimineerde unies vaak gebruikt in domeinmodellering voor het omsluiten van één type. Het is ook eenvoudig om de onderliggende waarde te extraheren via patroonkoppeling. U hoeft geen match-expressie 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 onderscheiden unies als structuren vertegenwoordigen. Dit gebeurt met het kenmerk [<Struct>]
.
[<Struct>]
type SingleCase = Case of string
[<Struct>]
type Multicase =
| Case1 of string
| Case2 of int
| Case3 of double
Omdat dit waardetypen zijn en geen referentietypen, zijn er extra overwegingen vergeleken met gediscrimineerde unies van verwijzingstypen.
- Ze worden gekopieerd als waardetypen en hebben semantiek van het waardetype.
- U kunt geen recursieve typedefinitie gebruiken met een gediscrimineerde unie met meerdere gevallen.
Vóór F# 9 was er een vereiste voor elk geval om een unieke casenaam (binnen de samenvoeging) op te geven. Vanaf F# 9 wordt de beperking opgeheven.
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 met afgeleide typen 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 unies kunnen worden gebruikt om boomstructuren te maken, die worden gebruikt om expressies in programmeertalen te modelleren. In de volgende code wordt een recursieve gediscrimineerde unie gebruikt om een binaire boomstructuur te maken. De uniestructuur bestaat uit twee gevallen: Node
, een knooppunt met een geheelgetalwaarde en linker- en rechter-subboom, en Tip
, dat de boom beëindigt.
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 heeft resultSumTree
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 het 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 functie Evaluate
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 van 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}"
.Is*
eigenschappen van gevallen
Sinds F# 9 stellen gediscrimineerde unies automatisch gegenereerde .Is*
-eigenschappen voor elk geval beschikbaar, zodat u kunt controleren of een waarde tot een bepaald geval behoort.
Dit is hoe deze kan worden gebruikt:
type Contact =
| Email of address: string
| Phone of countryCode: int * number: string
type Person = { name: string; contact: Contact }
let canSendEmailTo person =
person.contact.IsEmail // .IsEmail is auto-generated
Algemene kenmerken
De volgende kenmerken worden vaak gezien in gediscrimineerde vakbonden:
[<RequireQualifiedAccess>]
[<NoEquality>]
[<NoComparison>]
[<Struct>]