Sdílet prostřednictvím


Rozlišovaná sjednocení (F#)

Rozlišovaná sjednocení poskytují podporu pro hodnoty, které mohou být počty pojmenovaných případů, případně každý má jiné hodnoty a typy.Rozlišovaná sjednocení jsou užitečná pro heterogenní data; data, která mohou mít zvláštní případy, včetně platných a chybových případů; data, která se liší v typu z jedné instance do druhé; a jako alternativu pro malé hierarchie objektů.Navíc rekurzivně diskriminovaná sjednocení ke znázornění stromových struktur dat.

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

Poznámky

Rozlišovaná sjednocení jsou podobná sjednocovacím typům v jiných jazycích, ale existují rozdíly.Jako v případě sjednocovacího typu v C++ nebo variantním typu v jazyce Visual Basic, data uložená v hodnotě nejsou pevná; může se jednat o některou z několika různých možností.Na rozdíl od sjednocení v těchto jiných jazycích je však každé možnosti udělen identifikátor případu.Identifikátory velikosti písmen jsou názvy pro různé typy hodnot, které by mohly být objekty tohoto typu. Hodnoty jsou volitelné.Pokud hodnoty nejsou k dispozici, případ je ekvivalentní případu výčtu.Pokud existují hodnoty, každá hodnota může být buď samostatná hodnota ze zadaného typu nebo n-tice, která agreguje více polí stejných nebo různých typů.Stejně jako v případě F# 3.1 můžete jednotlivá pole pojmenovat, ale název je volitelný, i když jsou pojmenovány jiná pole ve stejném případě.

Zvažte například následující typ deklarace Shape:

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é sjednocení Tvar, které může nabývat hodnot ve všech třech případech: Obdélník, Kruh a Hranol.Každý případ má jinou sadu polí.Obdélník využívá dvě pole s názvem, obě typu float, které mají názvy Šířka a Délka.Případ Kruh má pouze jedno pole s názvem, poloměr.Případ hranolu obsahuje tři pole, kde dvě z nich jsou pojmenovány jako Nepojmenované pole a jsou označovány jako anonymní pole.

Objekty je možné vytvořit 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, můžete použít buď pojmenovaná pole v inicializaci, nebo že můžete spoléhat na pořadí polí v deklaraci a jenom naopak poskytnout hodnoty pro každé pole.Volání konstruktoru pro rect v předcházejícím kódu používá pojmenovaná pole, ale volání konstruktoru pro circ používá řazení.Můžete kombinovat objednaná pole a pojmenovaná pole jako v konstrukci prism.

Typ option je jednoduchým diskriminovaným sjednocením v základní knihovně F#.Typ option je deklarován následovně.

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

Předchozí kód určuje, který typ Option je diskriminovaným sjednocením, které má dva případy Some a None.Případ Some má přidruženou hodnotu, která se skládá z jednoho anonymní pole, jehož typ je vyjádřen parametrem typu 'a.Případ None nemá přidruženou hodnotu.Proto typ option určuje obecný typ, který má buď hodnotu některého typu nebo žádnou hodnotu.Typ Option má také alias psaný malými písmeny option, který je běžněji používaný.

Identifikátory velikosti písmen slouží jako konstruktory diskriminovaného typu sjednocení.Například následující kód se používá k vytvoření hodnot typu option.

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

Identifikátory velikosti písmen se používají také u výrazů pro porovnávání se vzorky.Ve vzorku s odpovídajícím výrazem jsou k dispozici identifikátory hodnot spojené s jednotlivými případy.Například v následujícím kódu je x identifikátorem přiřazené hodnoty, která souvisí s případem Some typu option.

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

Ve vzoru porovnávání výrazů můžete použít pojmenovaná pole k určení diskriminovaných sjednocených shod.Pro typ Tvar, který byl deklarován dříve, můžete použít pojmenovaná pole, jak ukazuje následující kód pro extrahování hodnot polí.

let getShapeHeight shape =
    match shape with
    | Rectangle(height = h) -> h
    | Circle(radius = r) -> 2. * r
    | Prism(height = h) -> h

Za normálních okolností lze identifikátory případu použít bez jejich kvalifikace v názvu unie.Pokud chcete, aby název byl vždy kvalifikovaným názvem sjednocení, můžete použít atribut RequireQualifiedAccess pro definici typu sjednocení.

Použití rozlišovaných unionů místo hierarchií objektu

Můžete často použít diskriminované sjednocení jako jednodušší alternativu k malé hierarchii objektu.Například následující diskriminované sjednocení může být místo základní třídy Shape, která obsahuje odvozené typy pro kruh, čtverec, a tak dále.

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

Namísto použití virtuální metody pro výpočet plochy nebo obvodu tak, jako v objektově orientované implementaci, můžete použít k výpočtu těchto množství porovnávání vzorce pro větvení příslušných vzorců.V následujícím příkladu jsou použity různé vzorce pro výpočet plochy v závislosti na tvaru.

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í rozlišovaných unionů pro struktury dat stromu

Rozlišovaná sjednocení mohou být rekurzivní, což znamená, že samotné sjednocení mohou být součástí typů jednoho nebo více případů.Rekurzivní rozlišovaná sjednocení lze použít k vytvoření stromových struktur, které se používají pro modelování výrazů v programovacích jazycích.V následujícím kódu je rekurzivní diskriminované sjednocení použito k vytvoření struktury dat binárního stromu.Union se skládá ze dvou případů, Node, což je uzel s celočíselnou hodnotou a levým a pravým podstromem, 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 má resultSumTree hodnotu 10.Následující obrázek znázorňuje stromovou strukturu pro myTree.

Stromová struktura pro myTree

Diagram stromu pro rozlišovaná sjednocení

Rozlišovaná sjednocení pracují dobře, pokud uzly ve stromu jsou heterogenní.V následujícím kódu, typ Expression představuje abstraktní strom syntaxe výrazu v jednoduchém programovacím jazyce, který podporuje sčítání a násobení čísel a proměnných.Některé sjednocovací případy nejsou rekurzivní a představují buď čísla (Number) nebo proměnné (Variable).Ostatní případy jsou rekurzivní a představují operace (Add a Multiply), kde jsou jako operandy také výrazy.Funkce Evaluate používá odpovídající výraz pro rekurzivní 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.ofList [ "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í kódu je hodnota result 5.

Viz také

Další zdroje

Referenční dokumentace jazyka F#