F # 8: Syndicats discriminés

Notre aventure F # continue donc. Nous avons examiné certains des types de blocs de construction de base, tels que les enregistrements / tuples, il est maintenant temps de jeter un œil aux associations balisées.

Les unions étiquetées prennent en charge les valeurs, qui peuvent être l'une des valeurs possibles. Les valeurs possibles sont appelées «cas combinés» et prennent la forme indiquée ci-dessous:

case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 …] 

Ne vous inquiétez pas si cette syntaxe semble intimidante, alors ce qui se résume vraiment à la présence d'une étiquette afin que chaque cas puisse être reconnu (distingué) des autres, et le type de cas de combinaison. Le nom a certaines règles, telles que

  • Doit commencer par une majuscule
  • Il peut s'agir d'un identifiant, y compris le nom d'un type d'union. Cela peut être un peu déroutant, mais il est utile de décrire le cas de la fusion.

Voici un exemple de mauvais identifiant

image

Et voici à quoi pourrait ressembler quelque chose comme ça lorsque vous utilisez un identifiant de balise qui correspond au registre de l'union, qui, comme mentionné précédemment, est parfaitement valide

 type LabelUnionType = Int of int | String of string 

Création d'associations balisées


Alors, comment construire un cas d'unification? Eh bien, il existe différentes façons, vous pouvez utiliser l'une des approches suivantes:

 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 

Qui au démarrage peut donner les résultats suivants lorsqu'il est lancé via la fonction printfn (j'utilise le formateur% A ou% O printfn ci-dessous):

image

Vous pouvez à peu près utiliser n'importe quel type pour combiner des cas comme

  • Tuple
  • la documentation
  • Autres types

La seule règle est que le type doit être défini avant que votre cas d'union puisse l'utiliser.

Voici un exemple qui utilise un type de tuple en cas d'union:

 type unionUsingTuples = CCY of (int * String) | Rates of (int * decimal) ..... ..... let tupledUnion = (12, "GBP") 

Unions vides


Vous pouvez également utiliser des unions vides. Quels sont ceux pour lesquels vous ne spécifiez pas de type. Cela les rend beaucoup plus similaires aux valeurs d'énumération .NET standard. Voici un exemple:

 type Player = Cross | Nought .... .... let emptyUnion = Cross 

Que diriez-vous de cas similaires par type


Un œil d'aigle, comme vous, peut voir le problème. Que se passerait-il si nous avions quelque chose comme ça:

 type PurchaseOrders = Orders of (string * int) | Empty type ClientOrders = Orders of (string * int) | Empty 

Cela nous pose des problèmes, n'est-ce pas. Comment distinguerions-nous ces types d'alliances délimitées? Heureusement, nous pouvons adopter une approche pleinement qualifiée à ce sujet, nous pouvons donc simplement le faire, et tout fonctionnera comme prévu. Il convient de noter que vous pouvez aller plus loin et inclure le nom du module si le module est impliqué (nous en apprendrons plus à ce sujet dans le prochain article):

 let purchaseOrders = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let clientOrders = ClientOrders.Orders ("scrubbing brush", 23) 

Comparaison


Comme avec de nombreux types F #, les jointures délimitées ne sont considérées comme égales que si

  • La longueur de leur boîtier combiné est la même.
  • Les types de cas d'union correspondent.
  • Les valeurs de leurs cas d'association sont les mêmes.

Pas égal


Voici un exemple où l'égalité n'est pas respectée:

 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) 

image

Égal


Voici un exemple d'égalité. C'est quelque chose comme du code .NET normal, vous savez, si les membres sont les mêmes, ils ont les mêmes valeurs et leur nombre correct, alors c'est presque la même chose (si nous ignorons les codes de hachage qui sont):

 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) 

image

Il convient de noter que nous ne pouvons pas utiliser l'égalité lorsque nous devons pleinement qualifier les types d'unions, car ce sont des types différents, donc cela ne fonctionnera pas:

image

Modèles de comparaison


Ci-dessous, une petite fonction qui prend une union de carte et affiche les cas de l'union avec laquelle elle a été appelée, et renvoie simplement Unit (vide, si vous vous en souvenez des articles précédents de cette série):

 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 

image

Alors exactement ce qui se passe dans les coulisses


Alors maintenant, nous avons vu quelques exemples du fonctionnement des associations labellisées. Donc, à votre avis, ce qui pourrait arriver si nous avions une bibliothèque F # qui utilisait des associations de balisage, et nous avons décidé de l'utiliser à partir de C # / VB.NET. Pensez-vous que cela fonctionnera. Réponse: Je suis sûr que ce le sera. Je ferai un article entier sur Interop quelque part dans le futur, mais j'ai juste pensé qu'il pourrait être intéressant d'envisager une partie de cela maintenant pour les jointures étiquetées, car elles sont si différentes de tout ce que nous voyons dans la programmation .NET standard.

Prenons donc la carte ci-dessus, qui était ce code:

 type Card = ValueCard of int | Jack | Queen | King | Ace 

Et exécutez-le via un décompilateur tel que Reflector / DotPeek (tout ce que vous avez). J'ai utilisé DotPeek et obtenu ce code C # pour cette seule ligne F #. Donc, comme vous pouvez le voir, le compilateur F # a fait un excellent travail pour s'assurer que les types F # fonctionneront bien avec .NET normal, comme C # / VB.NET.

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

Cas récursifs (arborescences)


Les jointures étiquetées peuvent également être utilisées de manière récursive, où l'union elle-même peut être utilisée comme l'un des types dans un ou plusieurs cas. Cela rend les jointures marquées très adaptées à la modélisation des structures arborescentes, telles que:

  • Expressions mathématiques
  • Arbres de syntaxe abstraite
  • Xml

En fait, MSDN a de bons exemples.

Dans le code suivant, une union étiquetée récursive est utilisée pour créer une structure de données d'arbre binaire. L'union se compose de deux cas: Node, qui est un nœud avec une valeur entière et des sous-arbres gauche et droit, et Tip, qui complète l'arborescence.

La structure arborescente de myTree dans l'exemple ci-dessous est illustrée dans la figure ci-dessous:

image

Et voici comment modéliser myTree à l'aide de jointures balisées. Remarquez comment nous classons la jointure balisée comme l'un des cas de jointure. Dans ce cas, les cas de combinaison soit

  • Astuce (union vide, agit comme une énumération standard dans .NET)
  • Ou un tuple à 3 chiffres à partir d'un nombre, Tree, Tree

Il convient également de noter que la fonction sumTree est marquée avec le mot-clé rec. Qu'est-ce que ce sort magique fait avec notre fonction? Eh bien, cela marque les fonctions sumTree comme celles qui seront appelées récursivement. Sans le mot clé "rec" dans la fonction sumTree, le compilateur F # se plaindra. Dans ce cas, le compilateur générera l'erreur suivante.

image

Mais nous sommes de bons gars, et nous utiliserons les bons mots clés pour soutenir notre cas d'utilisation, alors nous continuons

 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 

image

MSDN a également un autre bon exemple que je pense que cela vaudrait la peine d'être volé (oui, j'en parle franchement maintenant. Je pense que pendant que vous les gars / filles extrayez quelque chose de cet exemple emprunté qui, comme Je dis clairement, je suis emprunté, je ne suis pas en affaires). Regardons cet exemple ici:

 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 

image

Source: https://habr.com/ru/post/fr470275/


All Articles