Megosztás a következőn keresztül:


Diszkriminált uniók

A diszkriminált egyesítők olyan értékeket támogatnak, amelyek számos nevesített eset egyikét jelenthetik, amelyek mindegyike különböző értékekkel és típusokkal rendelkezik. A megkülönböztető egyesítések heterogén adatokhoz hasznosak; olyan adatok, amelyek különleges eseteket tartalmazhatnak, beleértve az érvényes és a hibaeseteket is; az egyik példány típusától a másikig változó adatok; és a kis objektumhierarchiák alternatívaként. Emellett a rekurzív diszkriminált uniók a fa adatstruktúráit is képviselik.

Syntax

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

    [ member-list ]

Megjegyzések

A diszkriminált egyesítők hasonlóak a más nyelvekben használt egyesítőkhöz, de vannak különbségek. A C++ egyesítési típusához vagy a Visual Basic egy változattípusához hasonlóan az értékben tárolt adatok nem lesznek rögzítve; számos különböző lehetőség egyike lehet. Az ezekben a nyelvekben található egyesítőkkel ellentétben azonban minden lehetséges lehetőség egy kis- és nagybetűs azonosítót kap. Az esetazonosítók az ilyen típusú objektumok különböző lehetséges értékeinek nevei; az értékek megadása nem kötelező. Ha az értékek nincsenek jelen, az eset egyenértékű az enumerálási esetekkel. Ha értékek vannak jelen, minden érték lehet egy adott típusú egyetlen érték, vagy egy rekord, amely több mezőt összesít ugyanabból vagy különböző típusokból. Az egyes mezőknek adhat nevet, de a név megadása nem kötelező, még akkor is, ha ugyanabban az esetben más mezők is el vannak nevezve.

A diszkriminált szakszervezetek akadálymentessége alapértelmezés szerint a következő.public

Vegyük például egy alakzattípus következő deklarációját.

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

Az előző kód diszkriminált egyesítési alakzatot deklarál, amelynek értéke három eset bármelyike lehet: Téglalap, Kör és Prizma. Minden esethez más mezőkészlet tartozik. A Téglalap kisbetű két elnevezett mezővel rendelkezik, mindkettő típushoz floattartozik, és a nevek szélessége és hossza. A Circle-esetnek csak egy nevesített mezője van, sugár. A Prizma-eset három mezőből áll, amelyek közül kettő (szélesség és magasság) nevesített mező. A névtelen mezőket névtelen mezőknek nevezzük.

Objektumok létrehozásához meg kell adnia az elnevezett és névtelen mezők értékeit az alábbi példák szerint.

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

Ez a kód azt mutatja, hogy használhatja az elnevezett mezőket az inicializálásban, vagy támaszkodhat a deklaráció mezőinek sorrendjére, és csak az egyes mezők értékeit adja meg. Az előző kód konstruktorhívása rect a nevesített mezőket használja, a konstruktorhívás circ azonban a rendelést használja. A rendezett mezőket és az elnevezett mezőket a konstrukcióhoz prismhasonlóan kombinálhatja.

A option típus egy egyszerű diszkriminált egyesítés az F#-magtárban. A option típus a következőképpen van deklarálva.

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

Az előző kód azt határozza meg, hogy a típus Option egy diszkriminált unió, amelynek két esete van, Some és None. Az Some eset egy társított értékkel rendelkezik, amely egy névtelen mezőből áll, amelynek típusát a típusparaméter 'ajelöli. Az None esetnek nincs társított értéke. Így a option típus egy általános típust határoz meg, amely vagy valamilyen típusú vagy érték nélküli értékkel rendelkezik. A típus Option kisbetűs aliassal is rendelkezik, optionamelyet gyakrabban használnak.

Az esetazonosítók konstruktorként használhatók a diszkriminált egyesítési típushoz. A következő kód például a típus értékeinek option létrehozására szolgál.

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

Az esetazonosítók a mintaegyező kifejezésekben is használatosak. Egy mintamegfeleltetési kifejezésben az egyes esetekhez társított értékekhez azonosítók tartoznak. Az alábbi kódban x például a típus esetéhez Someoption társított értéknek megfelelő azonosító.

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

A mintaegyező kifejezésekben nevesített mezők használatával adhatja meg a megkülönböztető egyesítési egyezéseket. A korábban deklarált alakzattípushoz a nevesített mezőket használhatja, ahogy az alábbi kód mutatja a mezők értékeinek kinyeréséhez.

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

Az esetazonosítók általában anélkül használhatók, hogy az egyesítő nevével minősítenék őket. Ha azt szeretné, hogy a név mindig az egyesítő nevével legyen minősítve, a RequireQualifiedAccess attribútumot alkalmazhatja az egyesítő típusdefinícióra.

Diszkriminált uniók megszüntetése

Az F#-ban a diszkriminált uniókat gyakran használják a tartománymodellezésben egyetlen típus burkolásához. A mögöttes érték könnyen kinyerhető mintaegyeztetésen keresztül is. Egyetlen esethez nem kell egyező kifejezést használnia:

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

Az alábbi példa a következőket mutatja be:

type ShaderProgram = | ShaderProgram of id:int

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

A mintaegyeztetés közvetlenül a függvényparaméterekben is engedélyezett, így egyetlen esetet is kibonthat:

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

Diszkriminált szakszervezetek strukturálása

A diszkriminált uniókat strukturáltként is képviselheti. Ez az attribútummal [<Struct>] történik.

[<Struct>]
type SingleCase = Case of string

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

Mivel ezek értéktípusok, és nem referenciatípusok, további szempontokat is figyelembe kell venni a referencia-diszkriminált egyesítőkkel szemben:

  1. A program értéktípusként másolja őket, és értéktípus szemantikával rendelkezik.
  2. Nem használhat rekurzív típusdefiníciót többbetűs strukturált diszkriminált unióval.
  3. Egyedi esetneveket kell megadnia a többbetűs strukturált diszkriminált unióhoz.

Diszkriminált uniók használata objektumhierarchiák helyett

A diszkriminált egyesítést gyakran egyszerűbb alternatívaként használhatja egy kis objektumhierarchia helyett. Például a következő diszkriminált egyesítés használható egy Shape olyan alaposztály helyett, amely a kör, a négyzet és így tovább származtatott típusokkal rendelkezik.

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

A terület vagy szegély kiszámítására szolgáló virtuális módszer helyett, ahogyan egy objektumorientált implementációban használná, a megfelelő képleteknek megfelelő elágazási mintával is kiszámíthatja ezeket a mennyiségeket. Az alábbi példában az alakzattól függően különböző képletekkel számítjuk ki a területet.

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 kimenet a következő:

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

Diszkriminált uniók használata fa adatstruktúrákhoz

A diszkriminált szakszervezetek rekurzívak lehetnek, ami azt jelenti, hogy maga az unió egy vagy több eset típusába is belefoglalható. A rekurzív diszkriminált uniók fastruktúrák létrehozásához használhatók, amelyek a programozási nyelvek kifejezéseinek modellezésére szolgálnak. Az alábbi kódban egy rekurzív diszkriminált uniót használunk egy bináris fa adatstruktúrájának létrehozásához. Az egyesítés két esetből áll: Nodeegy egész számmal rendelkező csomópontból, valamint bal és jobb oldali részhalmazból, valamint Tipa fát lezáró csomópontból.

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

Az előző kódban resultSumTree a 10 értéket adja meg. Az alábbi ábrán a faszerkezet myTreelátható.

Diagram that shows the tree structure for myTree.

A diszkriminált egyesítők jól működnek, ha a fa csomópontjai heterogének. A következő kódban a típus Expression egy kifejezés absztrakt szintaxisfáját jelöli egy egyszerű programozási nyelven, amely támogatja a számok és változók összeadását és szorzását. Az egyesítő esetek némelyike nem rekurzív, és számokat (Number) vagy változókat (Variable) jelöl. Más esetek rekurzívak, és olyan műveleteket (Add és Multiply), ahol az operandusok is kifejezések. A Evaluate függvény egyezéskifejezést használ a szintaxisfa rekurzív feldolgozásához.

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

A kód végrehajtásakor az érték result 5.

Tagok

A diszkriminált szakszervezetek tagjait meg lehet határozni. Az alábbi példa bemutatja, hogyan definiálhat egy tulajdonságot, és hogyan valósíthat meg felületet:

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

Gyakori attribútumok

A diszkriminált szakszervezetekben gyakran az alábbi attribútumok jelennek meg:

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

Lásd még