Diskriminerade fackföreningar

Diskriminerade fackföreningar ger stöd för värden som kan vara ett av flera namngivna fall, eventuellt var och en med olika värden och typer. Diskriminerade fackföreningar är användbara för heterogena data. data som kan ha särskilda fall, inklusive giltiga och felfall. data som varierar i typ från en instans till en annan. och som ett alternativ för små objekthierarkier. Dessutom används rekursiva diskriminerade fackföreningar för att representera träddatastrukturer.

Syntax

[ attributes ]
type [accessibility-modifier] type-name =
    | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
    | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]

    [ member-list ]

Kommentarer

Diskriminerade fackföreningar liknar unionstyper på andra språk, men det finns skillnader. Precis som med en unionstyp i C++ eller en varianttyp i Visual Basic är data som lagras i värdet inte fasta. Det kan vara ett av flera olika alternativ. Till skillnad från fackföreningar på dessa andra språk ges dock var och en av de möjliga alternativen en ärendeidentifierare. Ärendeidentifierarna är namn på de olika möjliga typer av värden som objekt av den här typen kan vara. värdena är valfria. Om det inte finns några värden motsvarar ärendet ett uppräkningsfall. Om det finns värden kan varje värde antingen vara ett enda värde av en angiven typ eller en tuppel som aggregerar flera fält av samma eller olika typer. Du kan ge ett enskilt fält ett namn, men namnet är valfritt, även om andra fält i samma fall namnges.

Tillgängligheten för diskriminerade fackföreningar är publicsom standard .

Tänk dig till exempel följande deklaration av en formtyp.

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

Föregående kod deklarerar en diskriminerad union form, som kan ha värden för något av tre fall: Rektangel, Cirkel och Prism. Varje skiftläge har en annan uppsättning fält. Rektangelfallet har två namngivna fält, båda av typen float, som har namnens bredd och längd. Circle-fallet har bara ett namngivet fält, radie. Prism-skiftläget har tre fält, varav två (bredd och höjd) heter fält. Namnlösa fält kallas anonyma fält.

Du skapar objekt genom att ange värden för de namngivna och anonyma fälten enligt följande exempel.

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

Den här koden visar att du antingen kan använda de namngivna fälten i initieringen, eller så kan du förlita dig på ordningen på fälten i deklarationen och bara ange värdena för varje fält i tur och ordning. Konstruktorns anrop i rect föregående kod använder de namngivna fälten, men konstruktorns anrop för circ använder ordningen. Du kan blanda de ordnade fälten och namngivna fält, som i konstruktionen av prism.

Typen option är en enkel diskrimineringsunion i F#-kärnbiblioteket. Typen option deklareras enligt följande.

// The option type is a discriminated union.
type Option<'a> =
    | Some of 'a
    | None

Den tidigare koden anger att typen Option är en diskriminerad union som har två fall och SomeNone. Ärendet Some har ett associerat värde som består av ett anonymt fält vars typ representeras av typparametern 'a. Ärendet None har inget associerat värde. option Därför anger typen en allmän typ som antingen har ett värde av någon typ eller inget värde. Typen Option har också ett alias av gemener, option, som används oftare.

Ärendeidentifierarna kan användas som konstruktorer för den diskriminerade unionstypen. Följande kod används till exempel för att skapa värden av typen option .

let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None

Ärendeidentifierarna används också i mönstermatchningsuttryck. I ett mönstermatchningsuttryck tillhandahålls identifierare för de värden som är associerade med de enskilda fallen. I följande kod x är till exempel identifieraren med det värde som är associerat med fallet med Someoption typen .

let printValue opt =
    match opt with
    | Some x -> printfn "%A" x
    | None -> printfn "No value."

I mönstermatchningsuttryck kan du använda namngivna fält för att ange diskriminerade unionsmatchningar. För den formtyp som deklarerades tidigare kan du använda de namngivna fälten som följande kod visar för att extrahera fältens värden.

let getShapeWidth shape =
    match shape with
    | Rectangle(width = w) -> w
    | Circle(radius = r) -> 2. * r
    | Prism(width = w) -> w

Normalt kan ärendeidentifierarna användas utan att kvalificera dem med namnet på facket. Om du vill att namnet alltid ska vara kvalificerat med namnet på unionen kan du använda attributet RequireQualifiedAccess för definitionen av unionstyp.

Ta bort skrivning av diskriminerade fackföreningar

I F# Används ofta diskriminerade fackföreningar i domänmodellering för att omsluta en enda typ. Det är enkelt att extrahera det underliggande värdet via mönstermatchning också. Du behöver inte använda ett matchningsuttryck för ett enskilt ärende:

let ([UnionCaseIdentifier] [values]) = [UnionValue]

Följande exempel visar detta:

type ShaderProgram = | ShaderProgram of id:int

let someFunctionUsingShaderProgram shaderProgram =
    let (ShaderProgram id) = shaderProgram
    // Use the unwrapped value
    ...

Mönstermatchning tillåts också direkt i funktionsparametrar, så du kan packa upp ett enskilt ärende där:

let someFunctionUsingShaderProgram (ShaderProgram id) =
    // Use the unwrapped value
    ...

Struct Diskriminerade fackföreningar

Du kan också representera diskriminerade fackföreningar som structs. Detta görs med attributet [<Struct>] .

[<Struct>]
type SingleCase = Case of string

[<Struct>]
type Multicase =
    | Case1 of Case1 : string
    | Case2 of Case2 : int
    | Case3 of Case3 : double

Eftersom det här är värdetyper och inte referenstyper finns det extra överväganden jämfört med referensdiskriminerade fackföreningar:

  1. De kopieras som värdetyper och har värdetypssemantik.
  2. Du kan inte använda en rekursiv typdefinition med en multicase struct Discriminated Union.
  3. Du måste ange unika ärendenamn för en multicase struct Discriminated Union.

Använda diskriminerade fackföreningar i stället för objekthierarkier

Du kan ofta använda en diskriminerad union som ett enklare alternativ till en liten objekthierarki. Till exempel kan följande diskriminerade union användas i stället för en Shape basklass som har härledda typer för cirkel, kvadrat och så vidare.

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

I stället för en virtuell metod för att beräkna ett område eller en perimeter, som du skulle använda i en objektorienterad implementering, kan du använda mönstermatchning för att förgrena till lämpliga formler för att beräkna dessa kvantiteter. I följande exempel används olika formler för att beräkna området, beroende på formen.

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)

Utdata är följande:

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

Använda diskriminerade fackföreningar för träddatastrukturer

Diskriminerade fackföreningar kan vara rekursiva, vilket innebär att själva facket kan ingå i typen av ett eller flera fall. Rekursiva diskriminerade fackföreningar kan användas för att skapa trädstrukturer som används för att modellera uttryck i programmeringsspråk. I följande kod används en rekursiv diskrimineringsunion för att skapa en binär träddatastruktur. Unionen består av två fall, Node, som är en nod med ett heltalsvärde och vänster och höger underträd, och Tip, som avslutar trädet.

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

I föregående kod resultSumTree har värdet 10. Följande bild visar trädstrukturen för myTree.

Diagram that shows the tree structure for myTree.

Diskriminerade fackföreningar fungerar bra om noderna i trädet är heterogena. I följande kod representerar typen Expression det abstrakta syntaxträdet för ett uttryck i ett enkelt programmeringsspråk som stöder addition och multiplikation av tal och variabler. Vissa av de fackliga fallen är inte rekursiva och representerar antingen tal (Number) eller variabler (Variable). Andra fall är rekursiva och representerar åtgärder (Add och Multiply), där operanderna också är uttryck. Funktionen Evaluate använder ett matchningsuttryck för att rekursivt bearbeta syntaxträdet.

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

När den här koden körs är värdet result 5.

Medlemmar

Det är möjligt att definiera medlemmar i diskriminerade fackföreningar. I följande exempel visas hur du definierar en egenskap och implementerar ett gränssnitt:

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}"

Vanliga attribut

Följande attribut ses ofta i diskriminerade fackföreningar:

  • [<RequireQualifiedAccess>]
  • [<NoEquality>]
  • [<NoComparison>]
  • [<Struct>]

Se även