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 'a
typu . 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, option
který 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:
- Zkopírují se jako typy hodnot a mají sémantiku typu hodnoty.
- Nelze použít rekurzivní definici typu s multicase struct Diskrimind Union.
- 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ů, Node
což 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
.
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>]