F#8:歧视工会

因此,我们的F#旅程继续进行。 我们研究了一些基本的构造块类型,例如记录/元组,现在是时候看看标记的关联了。

带标签的联合为值提供支持,该值可以是多个可能值之一。 可能的值称为“组合情况”,其格式如下所示:

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

不用担心这种语法是否令人生畏,然后真正归结为标签的存在,以便可以区分(区分)每种情况以及组合情况的类型。 该名称具有某些规则,例如

  • 必须以大写字母开头
  • 它可以是一个标识符,包括联合类型本身的名称。 这可能有点令人困惑,但是描述合并的情况很有用

这是错误ID的示例

图片

这就是使用与联合寄存器匹配的标签标识符时的样子,如前所述,它是完全有效的

 type LabelUnionType = Int of int | String of string 

建立标记的关联


那么如何建立统一案例呢? 嗯,有多种方法,您可以使用以下方法之一:

 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 

通过printfn函数启动时,在启动时可以给出以下结果(我在下面使用%A或%O printfn格式化程序):

图片

您几乎可以在合并情况下使用任何类型,例如

  • 元组
  • 文档
  • 其他种类

唯一的规则是必须先定义类型,然后您的联合案例才能使用它。

这是在联合的情况下使用元组类型的示例:

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

空联盟


您也可以使用空联合。 您未指定类型的是哪些。 这使它们与标准.NET枚举值更加相似。 这是一个例子:

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

类型相似的情况如何


像您一样,鹰眼可以看到问题所在。 如果我们有这样的事情会发生什么:

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

这给我们带来了问题,不是吗。 我们如何区分这些类型的已划分联盟? 幸运的是,我们可以对此采取完全合格的方法,因此我们可以做到这一点,并且一切都会按预期进行。 应该注意的是,如果涉及到模块,则可以更进一步,并包括模块名称(我们将在下一篇文章的后面部分中详细了解):

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

比较方式


与许多F#类型一样,只有在以下情况下,定界连接才被视为相等

  • 他们的合并案件的长度是相同的。
  • 他们的联合案例的类型匹配。
  • 他们的关联案例的价值

不相等


这是一个不尊重平等的例子:

 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) 

图片

平等


这是平等的一个例子。 就像普通的.NET代码一样,您知道,如果成员相同,则它们具有相同的值和正确的编号,那么这几乎是相同的(如果忽略了哈希代码):

 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) 

图片

应该指出的是,当我们必须完全限定联合的类型时,我们不能使用平等,因为它们是不同的类型,所以这将不起作用:

图片

比较模式


下面是一个小函数,它接受Card联合并显示调用其的联合的情况,并仅返回Unit(无效,如果您还记得本系列以前的文章中的内容):

 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 

图片

那么幕后到底发生了什么


因此,现在我们看到了一些有关标签关联如何工作的示例。 因此,在您看来,如果我们有一个使用标记联接的F#库并且我们决定从C#/ VB.NET使用它,那将会发生什么。 您认为这行得通吗? 答:我肯定会的。 我将在以后的某个地方写一篇有关Interop的完整文章,但我只是认为现在考虑将其中一些用于带标签的联接可能很有趣,因为它们与我们在标准.NET编程中所看到的完全不同。

因此,让我们以上面的Card作为代码:

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

并通过反编译器(例如Reflector / DotPeek)运行它(您所拥有的)。 我使用DotPeek并获得了这条F#行的C#代码。 因此,正如您所看到的,F#编译器在确保F#类型与常规.NET(例如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; } } } } } 

递归案例(树结构)


带标签的联接也可以递归方式使用,在一种或多种情况下,联合本身可以用作类型之一。 这使得标记的联接非常适合对树结构建模,例如:

  • 数学表达式
  • 抽象语法树
  • Xml

实际上,MSDN有一些很好的例子。

在以下代码中,使用递归标记的并集来创建二进制树数据结构。 联合由两种情况组成:Node(节点)是具有整数值并带有左和右子树的节点; Tip(完成),它完成了树。

下例中myTree的树结构如下图所示:

图片

这就是我们如何使用标记的联接对myTree建模。 注意我们如何将标记的联接分类为联接案例之一。 在这种情况下,合并

  • 提示(空联合,充当.NET中的标准枚举)
  • 或数字,树,树中的3位元组

还应注意,sumTree函数用关键字rec标记。 这个魔术对我们的功能有什么作用? 好吧,这将sumTree函数标记为将被递归调用的函数。 在sumTree函数中没有“ rec”关键字的情况下,F#编译器将抱怨。 在这种情况下,编译器将引发以下错误。

图片

但是我们是好人,我们将使用正确的关键字来支持我们的用例,因此我们继续

 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还有另一个很好的例子,我认为这是值得窃取的(是的,我现在正在坦率地谈论它。我认为,当你们正在从这个借来的例子中提取某些东西时,例如我清楚地说,我是借来的,我没有生意)。 让我们在这里看这个例子:

 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 

图片

Source: https://habr.com/ru/post/zh-CN470275/


All Articles