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 publicop .

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, optiondie 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:

  1. Ze worden gekopieerd als waardetypen en hebben semantiek van het waardetype.
  2. U kunt geen recursieve typedefinitie gebruiken met een gediscrimineerde samenvoeging met meerdere letters.
  3. 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, Nodeeen 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.

Diagram that shows the tree structure for 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>]

Zie ook