Diskriminierte Gewerkschaften
Diskriminierte Gewerkschaften unterstützen Werte, die einer der verschiedenen benannten Fälle sein können, möglicherweise mit unterschiedlichen Werten und Typen. Diskriminierte Gewerkschaften sind für heterogene Daten nützlich; Daten, die spezielle Fälle haben können, einschließlich gültiger und Fehlerfälle; Daten, die von einer Instanz zu einer anderen unterschiedlich sind; und als Alternative für kleine Objekthierarchien. Darüber hinaus werden rekursive diskriminierte Verbände verwendet, um Baumstrukturen darzustellen.
Syntax
[ attributes ]
type [accessibility-modifier] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
[ member-list ]
Bemerkungen
Diskriminierte Gewerkschaften ähneln gewerkschaftstypen in anderen Sprachen, aber es gibt Unterschiede. Wie bei einem Union-Typ in C++ oder einem Variant-Typ in Visual Basic sind die im Wert gespeicherten Daten nicht festgelegt; es kann sich um eine von mehreren verschiedenen Optionen handeln. Im Gegensatz zu Gewerkschaften in diesen anderen Sprachen erhält jedoch jede der möglichen Optionen eine Fall-ID. Die Fallbezeichner sind Namen für die verschiedenen möglichen Typen von Werten, die Objekte dieses Typs haben können, die Werte sind optional. Wenn keine Werte vorhanden sind, entspricht der Fall einem Enumerationsfall. Wenn Werte vorhanden sind, kann jeder Wert entweder ein einzelner Wert eines angegebenen Typs oder ein Tupel sein, das mehrere Felder desselben oder verschiedener Typen aggregiert. Sie können einem einzelnen Feld einen Namen geben, aber der Name ist optional, auch wenn andere Felder im selben Fall benannt sind.
Barrierefreiheit für diskriminierte Gewerkschaften sind standardmäßig public
.
Betrachten Sie beispielsweise die folgende Deklaration eines Shape-Typs.
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
Der vorangehende Code deklariert eine Unterscheidungs-Union-Form, die Werte der folgenden drei Fälle haben kann: Rechteck, Kreis und Prisma. Jeder Fall weist einen anderen Satz von Feldern auf. Das Rechteck-Objekt hat zwei benannte Felder, die beide vom Typ float
sind, mit den Namen Breite und Länge. Der Kreisfall hat nur ein benanntes Feld, nämlich Radius. Der Fall Prism weist drei Felder auf, von denen zwei (Breite und Höhe) benannte Felder sind. Unbenannte Felder werden als anonyme Felder bezeichnet.
Sie erstellen Objekte, indem Sie Werte für die benannten und anonymen Felder gemäß den folgenden Beispielen angeben.
let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)
Dieser Code zeigt, dass Sie entweder die benannten Felder in der Initialisierung verwenden können, oder Sie können sich auf die Reihenfolge der Felder in der Deklaration verlassen und nur die Werte für jedes Feld wiederum angeben. Der Konstruktoraufruf für rect
im vorherigen Code verwendet die benannten Felder, aber der Konstruktoraufruf für circ
verwendet die Sortierung. Sie können die angeordneten Felder und benannten Felder wie in der Konstruktion von prism
kombinieren.
Der option
-Typ ist eine einfache Unterscheidungs-Union in der F#-Kernbibliothek. Der option
Typ wird wie folgt deklariert.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
Der vorherige Code gibt an, dass der Typ Option
eine diskriminierte Vereinigung mit zwei Fällen, Some
und None
ist. Der fall Some
hat einen zugeordneten Wert, der aus einem anonymen Feld besteht, dessen Typ durch den Typparameter 'a
dargestellt wird. Der fall None
hat keinen zugeordneten Wert. Daher gibt der option
Typ einen generischen Typ an, der entweder einen Wert eines Typs oder keinen Wert aufweist. Der Typ Option
verfügt auch über einen kleingeschriebenen Alias, option
, der häufiger verwendet wird.
Die Fallbezeichner können als Konstruktoren für den diskriminierten Vereinigungstyp verwendet werden. Der folgende Code wird beispielsweise verwendet, um Werte des option
Typs zu erstellen.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
Die Fallbezeichner werden auch in Mustererkennungsausdrücken verwendet. In einem Musterabgleichsausdruck werden Bezeichner für die Werte bereitgestellt, die den einzelnen Fällen zugeordnet sind. Beispielsweise ist im folgenden Code x
der Bezeichner, dem der Wert zugeordnet ist, der mit dem Fall Some
des Typs option
verbunden ist.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
in Mustervergleichsausdrücken können Sie benannte Felder verwenden, um Unterscheidungs-Union-Übereinstimmungen anzugeben. Für den zuvor deklarierten Shape-Typ können Sie die benannten Felder wie der folgende Code verwenden, um die Werte der Felder zu extrahieren.
let getShapeWidth shape =
match shape with
| Rectangle(width = w) -> w
| Circle(radius = r) -> 2. * r
| Prism(width = w) -> w
Normalerweise können die Fall-IDs verwendet werden, ohne sie mit dem Namen der Vereinigung zu qualifizieren. Wenn der Name immer mit dem Namen der Union qualifiziert werden soll, können Sie das attribut RequireQualifiedAccess auf die Union-Typdefinition anwenden.
Diskriminierte Gewerkschaften auspacken
In F# werden Diskriminierte Gewerkschaften häufig bei der Domänenmodellierung für den Umbruch eines einzelnen Typs verwendet. Es ist einfach, den zugrunde liegenden Wert auch über den Musterabgleich zu extrahieren. Sie müssen keinen Übereinstimmungsausdruck für einen einzelnen Fall verwenden:
let ([UnionCaseIdentifier] [values]) = [UnionValue]
Im folgenden Beispiel wird dies veranschaulicht:
type ShaderProgram = | ShaderProgram of id:int
let someFunctionUsingShaderProgram shaderProgram =
let (ShaderProgram id) = shaderProgram
// Use the unwrapped value
...
Musterabgleich ist auch direkt in Funktionsparametern zulässig, sodass Sie dort einen einzelnen Fall entpacken können:
let someFunctionUsingShaderProgram (ShaderProgram id) =
// Use the unwrapped value
...
Diskriminierte Gewerkschaften strukturieren
Sie können diskriminierte Vereinigungen auch als Struktur darstellen. Dies erfolgt mit dem attribut [<Struct>]
.
[<Struct>]
type SingleCase = Case of string
[<Struct>]
type Multicase =
| Case1 of string
| Case2 of int
| Case3 of double
Da es sich hierbei um Werttypen und keine Verweistypen handelt, gibt es zusätzliche Überlegungen im Vergleich zu Referenzdiskriminierten Gewerkschaften:
- Sie werden als Werttypen kopiert und weisen Eine Werttypsemantik auf.
- Sie können rekursive Typdefinitionen nicht mit einer diskriminierten Union mit mehreren Fällen verwenden.
Vor F# 9 gab es für jeden Fall eine Anforderung, einen eindeutigen Fallnamen (innerhalb der Union) anzugeben. Ab F# 9 wird die Einschränkung aufgehoben.
Verwenden diskriminierter Vereinigungen anstelle von Objekthierarchien
Häufig können Sie eine diskriminierte Vereinigung als einfachere Alternative zu einer kleinen Objekthierarchie verwenden. Beispielsweise könnte die folgende diskriminierte Vereinigung anstelle einer Shape
Basisklasse verwendet werden, die abgeleitete Typen für Kreis, Quadrat usw. enthält.
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
Anstatt einer virtuellen Methode zur Berechnung einer Fläche oder eines Umfangs, wie Sie es in einer objektorientierten Implementierung verwenden würden, können Sie Musterabgleich nutzen, um zu den entsprechenden Formeln zur Berechnung dieser Größen zu gelangen. Im folgenden Beispiel werden abhängig von der Form unterschiedliche Formeln zum Berechnen des Bereichs verwendet.
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)
Die Ausgabe lautet wie folgt:
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
Das Verwenden von diskriminierten Verbünden für Baumdatenstrukturen
Diskriminierte Gewerkschaften können rekursiv sein, was bedeutet, dass die Gewerkschaft selbst in den Typ eines oder mehrerer Fälle einbezogen werden kann. Rekursive diskriminierte Vereinigungen können verwendet werden, um Baumstrukturen zu erstellen, die verwendet werden, um Ausdrücke in Programmiersprachen zu modellieren. Im folgenden Code wird eine rekursive Unterscheidungs-Union verwendet, um eine binäre Strukturdatenstruktur zu erstellen. Die Verknüpfung besteht aus zwei Fällen: Node
, das ein Knoten mit einem ganzzahligen Wert und linken und rechten Unterbäumen ist, und Tip
, die den Baum beendet.
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
Im vorherigen Code weist resultSumTree
den Wert 10 auf. Die folgende Abbildung zeigt die Struktur für myTree
an.
Diskriminierte Gewerkschaften funktionieren gut, wenn die Knoten in der Struktur heterogen sind. Im folgenden Code stellt der Typ Expression
die abstrakte Syntaxstruktur eines Ausdrucks in einer einfachen Programmiersprache dar, die das Hinzufügen und Multiplikation von Zahlen und Variablen unterstützt. Einige der Union-Fälle sind nicht rekursiv und stellen entweder Zahlen (Number
) oder Variablen (Variable
) dar. Andere Fälle sind rekursiv und stellen Vorgänge dar (Add
und Multiply
), wobei die Operanden auch Ausdrücke sind. Die Evaluate
-Funktion verwendet einen Übereinstimmungsausdruck, um die Syntaxstruktur rekursiv zu verarbeiten.
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
Wenn dieser Code ausgeführt wird, ist der Wert von result
5.
Wechselseitig rekursive diskriminierte Unions
Diskriminierte Unions in F# können wechselseitig rekursiv sein. Das bedeutet, dass mehrere Union-Typen auf rekursive Weise aufeinander verweisen können. Dies ist hilfreich beim Modellieren hierarchischer oder miteinander verbundener Strukturen. Um gegenseitig rekursive diskriminierte Vereinigungen zu definieren, verwenden Sie das Schlüsselwort and
.
Betrachten Sie beispielsweise eine abstrakte Syntaxstruktur (AST)-Darstellung, in der Ausdrücke Anweisungen enthalten können, und Anweisungen können Ausdrücke enthalten:
type Expression =
| Literal of int
| Variable of string
| Operation of string * Expression * Expression
and Statement =
| Assign of string * Expression
| Sequence of Statement list
| IfElse of Expression * Statement * Statement
Member
Es ist möglich, Mitglieder für diskriminierte Gewerkschaften zu definieren. Das folgende Beispiel zeigt, wie Eine Eigenschaft definiert und eine Schnittstelle implementiert wird:
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}"
.Is*
Eigenschaften bei Fällen
Seit F# 9 machen diskriminierte Vereinigungen automatisch generierte .Is*
Eigenschaften für jeden Fall verfügbar, sodass Sie überprüfen können, ob ein Wert von einem bestimmten Fall ist.
So kann es verwendet werden:
type Contact =
| Email of address: string
| Phone of countryCode: int * number: string
type Person = { name: string; contact: Contact }
let canSendEmailTo person =
person.contact.IsEmail // .IsEmail is auto-generated
Allgemeine Attribute
Die folgenden Attribute werden häufig in diskriminierten Gewerkschaften gesehen:
[<RequireQualifiedAccess>]
[<NoEquality>]
[<NoComparison>]
[<Struct>]