Also geht unsere F # Reise weiter. Wir haben uns einige der Grundtypen von Bausteinen angesehen, z. B. Datensätze / Tupel. Jetzt ist es Zeit, einen Blick auf die markierten Assoziationen zu werfen.
Beschriftete Gewerkschaften unterstützen Werte, die einer von mehreren möglichen Werten sein können. Mögliche Werte werden als "kombinierte Fälle" bezeichnet und haben die folgende Form:
case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 …]
Machen Sie sich keine Sorgen, wenn diese Syntax einschüchternd aussieht. Dann kommt es wirklich darauf an, dass ein Label vorhanden ist, damit jeder Fall von den anderen erkannt (unterschieden) werden kann, und der Typ für den Fall der Kombination. Der Name hat bestimmte Regeln, wie z
- Muss mit einem Großbuchstaben beginnen
- Es kann sich um eine Kennung handeln, einschließlich des Namens des Vereinigungstyps. Dies mag etwas verwirrend sein, aber es ist nützlich, den Fall des Zusammenführens zu beschreiben.
Hier ist ein Beispiel für eine schlechte ID

Und so könnte so etwas aussehen, wenn eine Tag-ID verwendet wird, die mit dem Gewerkschaftsregister übereinstimmt, was, wie bereits erwähnt, vollkommen gültig ist
type LabelUnionType = Int of int | String of string
Aufbau markierter Assoziationen
Wie kann man also einen Fall der Vereinigung aufbauen? Nun, es gibt verschiedene Möglichkeiten, Sie können einen der folgenden Ansätze verwenden:
let currentLabelUnionType1 = 13 printfn "let currentLabelUnionType1 = 13" printfn "%O" currentLabelUnionType1 let currentLabelUnionType2 = Int 23 printfn "let currentLabelUnionType2 = Int 23" printfn "%O" currentLabelUnionType2 printfn "%A" currentLabelUnionType2 let currentLabelUnionType3 = "Cat" printfn "let currentLabelUnionType3 = \"Cat\"" printfn "%O" currentLabelUnionType3 printfn "%A" currentLabelUnionType3 let currentLabelUnionType4 = String "Cat" printfn "let currentLabelUnionType4 = String \"Cat\"" printfn "%O" currentLabelUnionType4 printfn "%A" currentLabelUnionType4
Was beim Start die folgenden Ergebnisse liefern kann, wenn es über die Funktion printfn gestartet wird (ich verwende den Druckformatierer% A oder% O printfn unten):

Sie können so ziemlich jeden Typ verwenden, um Fälle wie zu kombinieren
- Tupel
- die Dokumentation
- Andere Arten
Die einzige Regel ist, dass der Typ definiert werden muss, bevor Ihr Gewerkschaftsfall ihn verwenden kann.
Hier ist ein Beispiel, das in Fällen der Vereinigung einen Tupeltyp verwendet:
type unionUsingTuples = CCY of (int * String) | Rates of (int * decimal) ..... ..... let tupledUnion = (12, "GBP")
Leere Gewerkschaften
Sie können auch leere Gewerkschaften verwenden. In welchen Fällen geben Sie keinen Typ an. Dadurch sind sie den Standardwerten für die .NET-Aufzählung viel ähnlicher. Hier ist ein Beispiel:
type Player = Cross | Nought .... .... let emptyUnion = Cross
Wie wäre es mit ähnlichen Fällen nach Typ
Ein Adlerauge wie Sie kann das Problem erkennen. Was würde passieren, wenn wir so etwas hätten:
type PurchaseOrders = Orders of (string * int) | Empty type ClientOrders = Orders of (string * int) | Empty
Das verursacht uns Probleme, nicht wahr? Wie würden wir zwischen diesen Arten von abgegrenzten Allianzen unterscheiden? Glücklicherweise können wir diesbezüglich einen voll qualifizierten Ansatz verfolgen, sodass wir es einfach tun können und alles wie erwartet funktioniert. Es ist zu beachten, dass Sie noch einen Schritt weiter gehen und den Modulnamen angeben können, wenn das Modul betroffen ist (mehr dazu später im nächsten Artikel):
let purchaseOrders = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let clientOrders = ClientOrders.Orders ("scrubbing brush", 23)
Vergleich
Wie bei vielen F # -Typen werden begrenzte Verknüpfungen nur dann als gleich angesehen, wenn
- Die Länge ihres kombinierten Falls ist gleich.
- Die Arten ihrer Gewerkschaftsfälle stimmen überein.
- Die Werte ihrer Assoziationsfälle sind gleich.
Nicht gleich
Hier ist ein Beispiel, bei dem Gleichheit nicht respektiert wird:
let purchaseOrders1 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let purchaseOrders2 = PurchaseOrders.Orders ("10 pack of disks", 1) printfn "purchaseOrders1 = purchaseOrders2 %A" (purchaseOrders1 = purchaseOrders2)

Gleich
Hier ist ein Beispiel für Gleichheit. Dies ist so etwas wie normaler .NET-Code. Wenn die Mitglieder identisch sind, dieselben Werte und ihre korrekte Anzahl haben, ist dies fast identisch (wenn wir die vorhandenen Hash-Codes ignorieren):
let purchaseOrders1 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let purchaseOrders2 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) printfn "purchaseOrders1 = purchaseOrders2 %A" (purchaseOrders1 = purchaseOrders2)

Es sollte beachtet werden, dass wir Gleichheit nicht verwenden können, wenn wir die Gewerkschaftstypen vollständig qualifizieren müssen, da es sich um verschiedene Typen handelt, sodass dies nicht funktioniert:

Vergleichsmuster
Im Folgenden finden Sie eine kleine Funktion, die eine Kartenvereinigung verwendet, die Fälle der Vereinigung anzeigt, mit der sie aufgerufen wurde, und einfach Unit zurückgibt (ungültig, wenn Sie sich an frühere Artikel in dieser Reihe erinnern):
type Card = ValueCard of int | Jack | Queen | King | Ace .... .... let cardFunction card = match card with | ValueCard i -> printfn "its a value card of %A" i | Jack -> printfn "its a Jack" | Queen -> printfn "its a Jack" | King -> printfn "its a Jack" | Ace -> printfn "its a Ace" () //return unit //shows you how to pass it in without a Let binding do cardFunction (Card.ValueCard 8) //or you could use explicit Let binding if you do desire let aceCard = Ace do cardFunction aceCard

Also genau das, was hinter den Kulissen passiert
Jetzt haben wir einige Beispiele dafür gesehen, wie beschriftete Assoziationen funktionieren. Was könnte Ihrer Meinung nach passieren, wenn wir eine F # -Bibliothek hätten, die Markup-Assoziationen verwendet, und wir haben uns entschieden, sie von C # / VB.NET aus zu verwenden. Glaubst du, das wird funktionieren? Antwort: Ich bin sicher, dass es so sein wird. Ich werde irgendwann in der Zukunft einen ganzen Beitrag über
Interop schreiben , aber ich dachte nur, es könnte interessant sein, einige davon jetzt für markierte Joins in Betracht zu ziehen, da sie sich so sehr von allem unterscheiden, was wir in der Standard-.NET-Programmierung sehen.
Nehmen wir also die Karte oben, die dieser Code war:
type Card = ValueCard of int | Jack | Queen | King | Ace
Führen Sie es durch einen Dekompiler wie Reflector / DotPeek (alles, was Sie haben). Ich habe DotPeek verwendet und diesen C # -Code für diese einzelne F # -Zeile erhalten. Wie Sie sehen, hat der F # -Compiler hervorragende Arbeit geleistet, um sicherzustellen, dass F # -Typen mit normalem .NET wie C # / VB.NET gut funktionieren.
using Microsoft.FSharp.Core; using System; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [CompilationMapping(SourceConstructFlags.Module)] public static class Program { [EntryPoint] public static int main(string[] argv) { return 0; } [DebuggerDisplay("{__DebugDisplay(),nq}")] [CompilationMapping(SourceConstructFlags.SumType)] [Serializable] [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)] public class Card : IEquatable<Program.Card>, IStructuralEquatable, IComparable<Program.Card>, IComparable, IStructuralComparable { [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public int Tag { [DebuggerNonUserCode] get { return this._tag; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsValueCard { [DebuggerNonUserCode] get { return this.get_Tag() == 0; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Jack { [CompilationMapping(SourceConstructFlags.UnionCase, 1)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Jack; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsJack { [DebuggerNonUserCode] get { return this.get_Tag() == 1; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Queen { [CompilationMapping(SourceConstructFlags.UnionCase, 2)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Queen; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsQueen { [DebuggerNonUserCode] get { return this.get_Tag() == 2; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card King { [CompilationMapping(SourceConstructFlags.UnionCase, 3)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_King; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsKing { [DebuggerNonUserCode] get { return this.get_Tag() == 3; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Ace { [CompilationMapping(SourceConstructFlags.UnionCase, 4)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Ace; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsAce { [DebuggerNonUserCode] get { return this.get_Tag() == 4; } } static Card() { } [CompilationMapping(SourceConstructFlags.UnionCase, 0)] public static Program.Card NewValueCard(int item) { return (Program.Card) new Program.Card.ValueCard(item); } [CompilationMapping(SourceConstructFlags.UnionCase, 1)] public static Program.Card get_Jack() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Jack; } [CompilationMapping(SourceConstructFlags.UnionCase, 2)] public static Program.Card get_Queen() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Queen; } [CompilationMapping(SourceConstructFlags.UnionCase, 3)] public static Program.Card get_King() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_King; } [CompilationMapping(SourceConstructFlags.UnionCase, 4)] public static Program.Card get_Ace() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Ace; } public static class Tags { public const int ValueCard = 0; public const int Jack = 1; public const int Queen = 2; public const int King = 3; public const int Ace = 4; } [DebuggerTypeProxy(typeof (Program.Card.ValueCard\u0040DebugTypeProxy))] [DebuggerDisplay("{__DebugDisplay(),nq}")] [Serializable] [SpecialName] public class ValueCard : Program.Card { [CompilationMapping(SourceConstructFlags.Field, 0, 0)] [CompilerGenerated] [DebuggerNonUserCode] public int Item { [DebuggerNonUserCode] get { return this.item; } } } [SpecialName] internal class ValueCard\u0040DebugTypeProxy { [CompilationMapping(SourceConstructFlags.Field, 0, 0)] [CompilerGenerated] [DebuggerNonUserCode] public int Item { [DebuggerNonUserCode] get { return this._obj.item; } } } } }
Rekursive Fälle (Baumstrukturen)
Beschriftete Verknüpfungen können auch rekursiv verwendet werden, wobei die Vereinigung selbst in einem oder mehreren Fällen als einer der Typen verwendet werden kann. Dies macht die markierten Verknüpfungen sehr geeignet für die Modellierung von Baumstrukturen, wie zum Beispiel:
- Mathematische Ausdrücke
- Abstrakte Syntaxbäume
- Xml
In der Tat hat MSDN einige gute Beispiele.Im folgenden Code wird eine rekursiv gekennzeichnete Vereinigung verwendet, um eine binäre Baumdatenstruktur zu erstellen. Die Vereinigung besteht aus zwei Fällen: Knoten, der ein Knoten mit einem ganzzahligen Wert und linken und rechten Teilbäumen ist, und Tipp, der den Baum vervollständigt.
Die Baumstruktur für myTree im folgenden Beispiel ist in der folgenden Abbildung dargestellt:

Und so könnten wir myTree mit markierten Joins modellieren. Beachten Sie, wie wir den markierten Join als einen der Join-Fälle klassifizieren. In diesem Fall sind die Fälle der Kombination entweder
- Tipp (leere Vereinigung, fungiert als Standardaufzählung in .NET)
- Oder ein dreistelliges Tupel aus einer Zahl, Baum, Baum
Es ist auch zu beachten, dass die sumTree-Funktion mit dem Schlüsselwort rec gekennzeichnet ist. Was macht dieser Zauber mit unserer Funktion? Nun, dies markiert sumTree-Funktionen als diejenigen, die rekursiv aufgerufen werden. Ohne das Schlüsselwort "rec" in der sumTree-Funktion beschwert sich der F # -Compiler. In diesem Fall gibt der Compiler den folgenden Fehler aus.

Aber wir sind gute Leute, und wir werden die richtigen Schlüsselwörter verwenden, um unseren Anwendungsfall zu unterstützen, also fahren wir fort
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 printfn "Value of sumTree is %A" resultSumTree

MSDN hat auch ein anderes gutes Beispiel, von dem ich denke, dass es sich lohnt, es zu stehlen (ja, ich spreche jetzt offen darüber. Ich denke, während ihr Jungs / Mädchen etwas aus diesem geliehenen Beispiel extrahiert, wie z Ich sage ganz klar, ich bin geliehen, ich bin nicht im Geschäft. Schauen wir uns dieses Beispiel hier an:
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 printfn "Value of sumTree is %A" result
