Итак, наше путешествие по F# продолжается.
Мы рассмотрели некоторые основные типы строительных блоков, такие как записи/кортежи, теперь пришло время взглянуть на помеченные объединения.
Отмеченные союзы обеспечивают поддержку значений, которые могут быть одним из нескольких возможных значений.
Возможные значения известны как «комбинированные случаи» и принимают форму, показанную ниже:
Не волнуйтесь, если этот синтаксис выглядит устрашающе, на самом деле он сводится к наличию метки, позволяющей отличать каждый случай от других, и типа для случая объединения.case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 …]
Название имеет определенные правила, такие как
- Должно начинаться с заглавной буквы
- Может быть идентификатором, включая имя самого типа объединения.
Это может немного сбивать с толку, но полезно описать случай соединения.
И вот как что-то подобное может выглядеть при использовании идентификатора метки, соответствующего объединенному регистру, который, как говорилось ранее, совершенно действителен.
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 (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#, использующая дискриминационные объединения, и мы решили использовать ее из 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
Следующий код использует рекурсивное дискриминируемое соединение для создания структуры данных двоичного дерева.
Объединение состоит из двух случаев: Node, который представляет собой целочисленный узел с левым и правым поддеревьями, и Tip, который завершает дерево.
Древовидная структура myTree в примере ниже показана на рисунке ниже:
И вот как мы могли бы смоделировать myTree, используя размеченные объединения.
Обратите внимание, как мы рассматриваем само аннотированное соединение как один экземпляр соединения.
В этом случае случаи объединения либо
- Совет (пустой союз, действует как стандартное перечисление .
NET)
- Или трехзначный кортеж числа Дерево, Дерево
Без ключевого слова «rec» в функции sumTree компилятор 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
Теги: #.
NET #F#
-
Как Раздавать Приглашения В Google+
19 Oct, 24 -
Краткий Обзор Интернет-Магазина В Битрикс24
19 Oct, 24 -
Жизнь Современного Разработчика В Yumoney
19 Oct, 24 -
Идея Программы
19 Oct, 24 -
Android Froyo 2.2 Для Htc Hero
19 Oct, 24