Итак, наше путешествие F # продолжается. Мы рассмотрели некоторые основные типы строительных блоков, такие как записи / кортежи, теперь пришло время взглянуть на размеченные объединения.
Размеченные союзы обеспечивают поддержку значений, которые могут быть одним из нескольких возможных значений. Возможные значения известны как «объединенные случаи», и принимают форму, показанную ниже:
Не беспокойтесь, если этот синтаксис выглядит пугающе, то, к чему он действительно сводится, — это наличие метки, чтобы каждый случай можно было распознать (отличить) от других, и типа для случая объединения. Название имеет определенные правила, такие как
Вот пример плохого идентификатора
![image](https://habrastorage.org/getpro/habr/post_images/832/c8a/fe0/832c8afe07d8b70c609873c18652b27e.png)
И вот как может выглядеть нечто подобное при использовании идентификатора метки, который совпадает с регистром объединения, который, как было сказано ранее, совершенно действителен
Так как же построить случай объединения? Ну, есть разные способы, вы можете использовать один из следующих подходов:
Который при запуске может дать следующие результаты при запуске через функцию printfn (я использую форматтер %A или %O printfn ниже):
![image](https://habrastorage.org/getpro/habr/post_images/cf0/376/0be/cf03760be0b27e0621fb455e9e747871.png)
Вы можете в значительной степени использовать любой тип в случаях объединения, таких как
Единственное правило заключается в том, что тип должен быть определен до того, как ваш случай объединения сможет использовать его.
Вот пример, который использует тип кортежа в случаях объединения:
Вы также можете использовать пустые союзы. Которые являются теми, где вы не указываете тип. Это делает их намного более похожими на стандартные значения перечисления .NET. Вот пример:
Орлиный глаз, типа Вас, может увидеть проблему. Что случилось бы, если бы у нас было что-то подобное:
Это вызывает у нас проблемы, не так ли. Как бы мы различали эти типы разграниченных союзов? К счастью, мы можем использовать полностью квалифицированный подход к этому, поэтому мы можем просто сделать это, и все будет работать, как ожидалось. Следует отметить, что вы можете сделать еще один шаг вперед и включить имя модуля, если модуль задействован (об этом мы узнаем позже, в следующей статье):
Как и во многих типах F#, разграниченные объединения считаются равными, только если
Вот пример, где не соблюдается равенство:
![image](https://habrastorage.org/getpro/habr/post_images/6f7/16a/355/6f716a3554105e739c50e42de2846287.png)
Вот пример равенства. Это что-то вроде обычного кода .NET, вы знаете, если члены одинаковые, они имеют одинаковые значения и их правильное число, то это почти одно и то же (если мы игнорируем хеш-коды, которые есть):
![image](https://habrastorage.org/getpro/habr/post_images/f3a/df3/12d/f3adf312dd3da82030e5d77b694ba8ae.png)
Следует отметить, что мы не можем использовать равенство, когда мы должны полностью квалифицировать типы объединений, поскольку они являются различными типами, поэтому это не будет работать:
![image](https://habrastorage.org/getpro/habr/post_images/6ac/f7b/b25/6acf7bb25aba5d1778c173fd73c5efae.png)
Ниже показана небольшая функция, которая принимает объединение Card и выводит случаи объединения, с которыми она была вызвана, и просто возвращает Unit (void, если вы помните это из предыдущих статей этой серии):
![image](https://habrastorage.org/getpro/habr/post_images/ab7/a11/21e/ab7a1121eb25190c883400d09e939646.png)
Итак, теперь мы увидели несколько примеров того, как работают размеченные объединения. Итак, что, по вашему мнению, могло бы произойти, если бы у нас была библиотека F#, которая использовала размеченные объединения, и мы решили использовать ее из C # / VB.NET. Как вы думаете, это будет работать. Ответ: уверен, что будет. Я сделаю целый пост о Interop где-нибудь в будущем, но я просто подумал, что может быть интересно рассмотреть часть этого прямо сейчас для размеченных объединений, поскольку они настолько отличаются от всего, что мы видим в стандартном программировании .NET.
Итак, давайте возьмем Card выше, который был этот код:
И запустите его через декомпилятор, такой как Reflector / DotPeek (все, что у вас есть). Я использовал DotPeek и получил этот код C# для этой единственной строки F#. Итак, как вы можете видеть, компилятор F# проделал большую работу, чтобы убедиться, что типы F# будут хорошо взаимодействовать с обычными .NET, такими как C#/VB.NET.
Размеченные объединения могут также использоваться рекурсивным способом, где само объединение может использоваться как один из типов в одном или нескольких случаях. Это делает размеченные объединения очень подходящими для моделирования древовидных структур, таких как:
На самом деле MSDN имеет несколько хороших примеров на этот счет
В следующем коде рекурсивное размеченное объединение используется для создания структуры данных двоичного дерева. Объединение состоит из двух случаев: Node, который является узлом с целочисленным значением и левым и правым поддеревьями, и Tip, который завершает дерево.
Древовидная структура для myTree в приведенном ниже примере показана на рисунке ниже:
![image](https://habrastorage.org/getpro/habr/post_images/3dd/d4f/1f9/3ddd4f1f98668aa64a5d4b8577ca8ede.png)
И вот как мы могли бы смоделировать myTree, используя размеченные объединения. Обратите внимание, как мы относим само размеченное объединение в качестве одного из случаев объединения. В этом случае случаи объединения либо
Следует также отметить, что функция sumTree помечается ключевым словом rec. Что это волшебное заклинание делает с нашей функцией? Ну, это помечает функции sumTree как те, которые будут вызываться рекурсивно. Без ключевого слова «rec» в функции sumTree компилятор F# будет жаловаться. В этом случае компилятор выдаст следующую ошибку.
![image](https://habrastorage.org/getpro/habr/post_images/ea2/97f/878/ea297f878ef1675a089775fb38392d56.png)
Но мы хорошие ребята, и мы будем использовать правильные ключевые слова для поддержки нашего варианта использования, поэтому мы продолжаем
![image](https://habrastorage.org/getpro/habr/post_images/edd/bd4/a6d/eddbd4a6d557bd13c7b6d21ee11b73ba.png)
У MSDN также есть еще один хороший пример, который, я думаю, стоило бы украсть (да, сейчас я откровенно об этом говорю. Я думаю, что пока вы, ребята/девочки, извлекаете что-то из этого заимствованного примера, который, как я ясно говорю, заимствован, я не при делах). Давайте посмотрим на этот пример здесь:
Размеченные союзы обеспечивают поддержку значений, которые могут быть одним из нескольких возможных значений. Возможные значения известны как «объединенные случаи», и принимают форму, показанную ниже:
case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 …]
Не беспокойтесь, если этот синтаксис выглядит пугающе, то, к чему он действительно сводится, — это наличие метки, чтобы каждый случай можно было распознать (отличить) от других, и типа для случая объединения. Название имеет определенные правила, такие как
- Должен начинаться с заглавной буквы
- Может быть идентификатором, включая само имя типа объединения. Это может немного сбить с толку, но полезно описать случай объединения
Вот пример плохого идентификатора
![image](https://habrastorage.org/getpro/habr/post_images/832/c8a/fe0/832c8afe07d8b70c609873c18652b27e.png)
И вот как может выглядеть нечто подобное при использовании идентификатора метки, который совпадает с регистром объединения, который, как было сказано ранее, совершенно действителен
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 ниже):
![image](https://habrastorage.org/getpro/habr/post_images/cf0/376/0be/cf03760be0b27e0621fb455e9e747871.png)
Вы можете в значительной степени использовать любой тип в случаях объединения, таких как
- Кортеж
- документация
- Другие типы
Единственное правило заключается в том, что тип должен быть определен до того, как ваш случай объединения сможет использовать его.
Вот пример, который использует тип кортежа в случаях объединения:
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)
![image](https://habrastorage.org/getpro/habr/post_images/6f7/16a/355/6f716a3554105e739c50e42de2846287.png)
Равны
Вот пример равенства. Это что-то вроде обычного кода .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)
![image](https://habrastorage.org/getpro/habr/post_images/f3a/df3/12d/f3adf312dd3da82030e5d77b694ba8ae.png)
Следует отметить, что мы не можем использовать равенство, когда мы должны полностью квалифицировать типы объединений, поскольку они являются различными типами, поэтому это не будет работать:
![image](https://habrastorage.org/getpro/habr/post_images/6ac/f7b/b25/6acf7bb25aba5d1778c173fd73c5efae.png)
Паттерны сравнения
Ниже показана небольшая функция, которая принимает объединение 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
![image](https://habrastorage.org/getpro/habr/post_images/ab7/a11/21e/ab7a1121eb25190c883400d09e939646.png)
Так что именно то, что происходит за кулисами
Итак, теперь мы увидели несколько примеров того, как работают размеченные объединения. Итак, что, по вашему мнению, могло бы произойти, если бы у нас была библиотека F#, которая использовала размеченные объединения, и мы решили использовать ее из C # / VB.NET. Как вы думаете, это будет работать. Ответ: уверен, что будет. Я сделаю целый пост о Interop где-нибудь в будущем, но я просто подумал, что может быть интересно рассмотреть часть этого прямо сейчас для размеченных объединений, поскольку они настолько отличаются от всего, что мы видим в стандартном программировании .NET.
Итак, давайте возьмем Card выше, который был этот код:
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 в приведенном ниже примере показана на рисунке ниже:
![image](https://habrastorage.org/getpro/habr/post_images/3dd/d4f/1f9/3ddd4f1f98668aa64a5d4b8577ca8ede.png)
И вот как мы могли бы смоделировать myTree, используя размеченные объединения. Обратите внимание, как мы относим само размеченное объединение в качестве одного из случаев объединения. В этом случае случаи объединения либо
- Tip(пустое объединение, действует как стандартное перечисление в .NET)
- Или 3-х значный кортеж из числа, Tree, Tree
Следует также отметить, что функция sumTree помечается ключевым словом rec. Что это волшебное заклинание делает с нашей функцией? Ну, это помечает функции sumTree как те, которые будут вызываться рекурсивно. Без ключевого слова «rec» в функции sumTree компилятор F# будет жаловаться. В этом случае компилятор выдаст следующую ошибку.
![image](https://habrastorage.org/getpro/habr/post_images/ea2/97f/878/ea297f878ef1675a089775fb38392d56.png)
Но мы хорошие ребята, и мы будем использовать правильные ключевые слова для поддержки нашего варианта использования, поэтому мы продолжаем
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](https://habrastorage.org/getpro/habr/post_images/edd/bd4/a6d/eddbd4a6d557bd13c7b6d21ee11b73ba.png)
У 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
![image](https://habrastorage.org/getpro/habr/post_images/4f3/5cb/5ea/4f35cb5ea06ed795a8aa6a565ac72a37.png)
Drag13
Спасибо за Ваши переводы, это не проходит не замеченным. Но, пожалуйста, уделяйте больше внимания качеству перевода. Уже становится сложно читать.
Neftedollar
скорость переводов огромная, у автора глаз замылился уже.
Но в целом полезное дело, да.