F#8: Дискриминированные Профсоюзы

Итак, наше путешествие по F# продолжается.

Мы рассмотрели некоторые основные типы строительных блоков, такие как записи/кортежи, теперь пришло время взглянуть на помеченные объединения.

Отмеченные союзы обеспечивают поддержку значений, которые могут быть одним из нескольких возможных значений.

Возможные значения известны как «комбинированные случаи» и принимают форму, показанную ниже:

  
  
  
  
  
  
  
  
  
  
  
  
  
   

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

Не волнуйтесь, если этот синтаксис выглядит устрашающе, на самом деле он сводится к наличию метки, позволяющей отличать каждый случай от других, и типа для случая объединения.

Название имеет определенные правила, такие как

  • Должно начинаться с заглавной буквы
  • Может быть идентификатором, включая имя самого типа объединения.

    Это может немного сбивать с толку, но полезно описать случай соединения.

Вот пример плохого идентификатора

F#8: Дискриминированные профсоюзы

И вот как что-то подобное может выглядеть при использовании идентификатора метки, соответствующего объединенному регистру, который, как говорилось ранее, совершенно действителен.



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 ниже):

F#8: Дискриминированные профсоюзы

Вы можете использовать любой тип в случаях объединения, например
  • Кортеж
  • документация
  • Другие типы
Единственное правило состоит в том, что тип должен быть определен до того, как ваш случай объединения сможет его использовать.

Вот пример использования типа кортеж в случаях объединения:

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)



F#8: Дискриминированные профсоюзы



Равный

Вот пример равенства.

Это что-то вроде обычного .

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)



F#8: Дискриминированные профсоюзы

Следует отметить, что мы не можем использовать равенство, когда нам нужно полностью определить типы объединения, поскольку это разные типы, поэтому это не сработает:

F#8: Дискриминированные профсоюзы



Шаблоны сравнения

Ниже приведена небольшая функция, которая принимает объединение Card, распечатывает случаи объединения, с которым она была вызвана, и просто возвращает Unit (void, если вы помните это из предыдущих статей этой серии):

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#8: Дискриминированные профсоюзы



Так вот именно это и происходит за кулисами

Итак, теперь мы увидели несколько примеров того, как работают распознаваемые соединения.

Как вы думаете, что могло бы произойти, если бы у нас была библиотека F#, использующая дискриминационные объединения, и мы решили использовать ее из C#/VB.NET? Как вы думаете, это сработает. Ответ: Я уверен, что так и будет. Я сделаю целый пост о Взаимодействие где-то в будущем, но я просто подумал, что, возможно, было бы интересно прямо сейчас взглянуть на некоторые из этих аспектов для распределенных соединений, поскольку они сильно отличаются от всего, что мы видим в стандартном программировании .

NET. Итак, давайте возьмем карту выше, на которой был этот код:

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

И запустите его через декомпилятор, например Reflector/DotPeek (любой, который у вас есть).

Я использовал DotPeek и получил код C# для этой единственной строки F#.

Итак, как вы можете видеть, компилятор 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 в примере ниже показана на рисунке ниже:

F#8: Дискриминированные профсоюзы

И вот как мы могли бы смоделировать myTree, используя размеченные объединения.

Обратите внимание, как мы рассматриваем само аннотированное соединение как один экземпляр соединения.

В этом случае случаи объединения либо

  • Совет (пустой союз, действует как стандартное перечисление .

    NET)

  • Или трехзначный кортеж числа Дерево, Дерево
Также следует отметить, что функция sumTree отмечена ключевым словом Rec. Как это магическое заклинание влияет на нашу функцию? Что ж, это помечает функции sumTree как те, которые будут вызываться рекурсивно.

Без ключевого слова «rec» в функции sumTree компилятор F# будет жаловаться.

В этом случае компилятор выдаст следующую ошибку.



F#8: Дискриминированные профсоюзы

Но мы хорошие ребята и будем использовать правильные ключевые слова для поддержки нашего варианта использования, поэтому мы продолжаем

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



F#8: Дискриминированные профсоюзы

У 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



F#8: Дискриминированные профсоюзы

Теги: #.

NET #F#

Вместе с данным постом часто просматривают: