Rozlišovaná sjednocení

Diskriminované sjednocení poskytují podporu pro hodnoty, které mohou být jedním z několika pojmenovaných případů, případně každý s různými hodnotami a typy. Diskriminované sjednocení jsou užitečné pro heterogenní data; údaje, které mohou mít zvláštní případy, včetně platných a chybových případů; data, která se liší podle typu od jedné instance po jinou; a jako alternativu pro malé hierarchie objektů. Rekurzivní diskriminované sjednocení se navíc používají k reprezentaci stromových datových struktur.

Syntaxe

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

    [ member-list ]

Poznámky

Diskriminované sjednocení jsou podobné typům sjednocení v jiných jazycích, ale existují rozdíly. Stejně jako u sjednocovacího typu v jazyce C++ nebo variantního typu v jazyce Visual Basic nejsou data uložená v hodnotě pevná; může to být jedna z několika různých možností. Na rozdíl od sjednocení v těchto jiných jazycích se ale každé z možných možností dá identifikátor případu. Identifikátory případu jsou názvy různých možných typů hodnot, které mohou být objekty tohoto typu; hodnoty jsou volitelné. Pokud hodnoty nejsou k dispozici, případ odpovídá případu výčtu. Pokud existují hodnoty, může být každá hodnota buď jedna hodnota zadaného typu, nebo řazená kolekce členů, která agreguje více polí stejného nebo různých typů. Jednotlivé pole můžete pojmenovat, ale název je nepovinný, i když jsou pojmenována jiná pole ve stejném případě.

Přístupnost pro diskriminované sjednocení ve výchozím nastavení public.

Představte si například následující deklaraci typu obrazce.

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

Předchozí kód deklaruje diskriminovaný sjednocovací obrazec, který může mít hodnoty ze tří případů: Rectangle, Circle a Prism. Každý případ má jinou sadu polí. Obdélníkový případ má dvě pojmenovaná pole, obě typu float, které mají názvy šířky a délky. Velikost písmen kruhu má pouze jedno pojmenované pole, poloměr. Případ Prism má tři pole, z nichž dvě (šířka a výška) jsou pojmenovaná pole. Nepojmenovaná pole se označují jako anonymní pole.

Objekty vytvoříte zadáním hodnot pro pojmenovaná a anonymní pole podle následujících příkladů.

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

Tento kód ukazuje, že v inicializaci můžete buď použít pojmenovaná pole, nebo se můžete spolehnout na řazení polí v deklaraci a zadat hodnoty pro jednotlivá pole zase. Volání konstruktoru rect v předchozím kódu používá pojmenovaná pole, ale volání konstruktoru pro circ použití řazení. Uspořádaná pole a pojmenovaná pole můžete kombinovat, stejně jako při konstrukci prism.

Typ option je jednoduchá diskriminovaná sjednocení v základní knihovně jazyka F#. Typ option je deklarován následujícím způsobem.

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

Předchozí kód určuje, že typ Option je diskriminovaná sjednocení, která má dva případy, Some a None. Případ Some má přidruženou hodnotu, která se skládá z jednoho anonymního pole, jehož typ je reprezentován parametrem 'atypu . Případ None nemá přidruženou hodnotu. option Typ tedy určuje obecný typ, který má buď hodnotu určitého typu, nebo žádnou hodnotu. Option Typ má také alias malého typu, optionkterý se běžně používá.

Identifikátory případů lze použít jako konstruktory pro diskriminovaný typ sjednocení. Například následující kód slouží k vytvoření hodnot option typu.

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

Identifikátory případů se také používají ve vzorových shodných výrazech. Ve vzorovém shodném výrazu jsou identifikátory zadané pro hodnoty přidružené k jednotlivým případům. Například v následujícím kódu x je identifikátor vzhledem k hodnotě, která je přidružena k Some případu option typu.

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

Ve vzorových shodných výrazech můžete pomocí pojmenovaných polí určit diskriminované sjednocování shody. Pro typ obrazce, který byl deklarován dříve, můžete použít pojmenovaná pole jako následující kód k extrahování hodnot polí.

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

Za normálních okolností je možné použít identifikátory případů bez jejich kvalifikace s názvem sjednocení. Pokud chcete, aby byl název vždy kvalifikovaný s názvem sjednocení, můžete použít atribut RequireQualifiedAccess na definici typu sjednocení.

Unwrapping Diskrimind Unions

V F# Diskriminované sjednocení se často používají při modelování domény pro zabalení jednoho typu. Základní hodnotu můžete snadno extrahovat také pomocí porovnávání vzorů. Pro jedno velké písmeno nemusíte používat výraz shody:

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

Následující příklad ukazuje toto:

type ShaderProgram = | ShaderProgram of id:int

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

Porovnávání vzorů je také povolené přímo v parametrech funkce, takže můžete vybalit jeden případ:

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

Diskriminované sjednocení struktur

Můžete také reprezentovat diskriminované sjednocení jako struktury. To se provádí pomocí atributu [<Struct>] .

[<Struct>]
type SingleCase = Case of string

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

Vzhledem k tomu, že se jedná o typy hodnot, nikoli odkazové typy, existují další aspekty v porovnání s odkazovými diskriminovanými sjednoceními:

  1. Zkopírují se jako typy hodnot a mají sémantiku typu hodnoty.
  2. Nelze použít rekurzivní definici typu s multicase struct Diskrimind Union.
  3. Je nutné zadat jedinečné názvy malých a velkých písmen pro multicase diskriminované sjednocení.

Použití diskriminovaných sjednocení místo hierarchií objektů

Diskriminovanou sjednocení můžete často použít jako jednodušší alternativu k malé hierarchii objektů. Například následující diskriminovaná sjednocení lze použít místo Shape základní třídy, která má odvozené typy pro kruh, čtverec atd.

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

Místo virtuální metody pro výpočet oblasti nebo obvodu, jak byste použili v objektově orientované implementaci, můžete použít porovnávání vzorů k větvení s příslušnými vzory pro výpočet těchto množství. V následujícím příkladu se k výpočtu oblasti v závislosti na obrazci používají různé vzorce.

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)

Výstup je následující:

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

Použití diskriminovaných sjednocení pro stromové datové struktury

Diskriminované sjednocení mohou být rekurzivní, což znamená, že samotná unie může být zahrnuta do typu jednoho nebo více případů. Rekurzivní diskriminované sjednocení lze použít k vytváření stromových struktur, které se používají k modelování výrazů v programovacích jazycích. V následujícím kódu se rekurzivní diskriminovaná sjednocení používá k vytvoření datové struktury binárního stromu. Sjednocení se skládá ze dvou případů, Nodecož je uzel s celočíselnou hodnotou a levým a pravým podstromy a Tip, který ukončí strom.

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

V předchozím kódu resultSumTree má hodnotu 10. Následující obrázek znázorňuje strukturu stromu pro myTree.

Diagram that shows the tree structure for myTree.

Diskriminované sjednocení fungují dobře, pokud jsou uzly ve stromu heterogenní. V následujícím kódu typ Expression představuje abstraktní syntaxi stromu výrazu v jednoduchém programovacím jazyce, který podporuje sčítání a násobení čísel a proměnných. Některé případy sjednocení nejsou rekurzivní a představují čísla (Number) nebo proměnné (Variable). Jiné případy jsou rekurzivní a představují operace (Add a Multiply), kde operandy jsou také výrazy. Funkce Evaluate používá výraz shody k rekurzivnímu zpracování stromu syntaxe.

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

Při spuštění tohoto kódu je hodnota result 5.

Členové

Je možné definovat členy na diskriminovaných svazech. Následující příklad ukazuje, jak definovat vlastnost a implementovat rozhraní:

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

Běžné atributy

V diskriminovaných sjednoceních se běžně používají následující atributy:

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

Viz také