Размеченные объединения

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

Синтаксис

[ attributes ]
type [accessibility-modifier] type-name =
    | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
    | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]

    [ member-list ]

Remarks

Размеченные объединения похожи на типы объединения на других языках, но существуют различия. как и тип объединения в C++ или тип variant в Visual Basic, данные, хранящиеся в значении, не являются фиксированными; Это может быть один из нескольких различных параметров. Однако в отличие от объединений в этих других языках, каждому из возможных параметров присваивается идентификатор варианта. Идентификаторы вариантов — это имена различных возможных типов значений, которые могут быть объектами этого типа. значения являются необязательными. Если значения не указаны, регистр эквивалентен регистру перечисления. При наличии значений каждое значение может быть одним значением указанного типа или кортежем, который объединяет несколько полей одного или разных типов. Можно присвоить имя отдельному полю, но имя является необязательным, даже если другие поля в том же регистре называются.

По умолчанию специальные возможности для размеченных объединений имеют значение public .

Например, рассмотрим следующее объявление типа фигуры.

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

Приведенный выше код объявляет фигуру размеченного объединения, которая может иметь значения любого из трех вариантов: прямоугольника, круга и Prism. Каждый вариант имеет другой набор полей. В прямоугольнике есть два именованных поля типа float , которые имеют имена Width и length. В круглом варианте есть только одно именованное поле, RADIUS. В Prism Case есть три поля, два из которых (ширина и высота) называются полями. Безымянные поля называются анонимными полями.

Объекты создаются путем предоставления значений для именованных и анонимных полей в соответствии со следующими примерами.

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

Этот код показывает, что можно либо использовать именованные поля в инициализации, либо полагаться на порядок полей в объявлении и просто предоставить значения для каждого поля в свою очередь. При вызове конструктора для rect в предыдущем коде используются именованные поля, но при вызове конструктора для circ используется упорядочение. Можно смешивать упорядоченные поля и именованные поля, как в конструкции prism .

optionТип является простым размеченное объединением в основной библиотеке F #. optionТип объявляется следующим образом.

// The option type is a discriminated union.
type Option<'a> =
    | Some of 'a
    | None

Предыдущий код указывает, что тип Option — это размеченное объединение, которое имеет два варианта: Some и None . SomeВариант имеет связанное значение, состоящее из одного анонимного поля, тип которого представлен параметром типа 'a . NoneРегистр не имеет связанного значения. Таким же option типом определяется универсальный тип, который либо имеет значение некоторого типа, либо не имеет значения. Тип Option также имеет псевдоним типа "нижний регистр", option который чаще всего используется.

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

let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None

Идентификаторы вариантов также используются в выражениях сопоставления шаблонов. В выражении сопоставления шаблонов идентификаторы предоставляются для значений, связанных с отдельными вариантами. Например, в следующем коде x является идентификатором, заданным значением, связанным с Some вариантом option типа.

let printValue opt =
    match opt with
    | Some x -> printfn "%A" x
    | None -> printfn "No value."

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

let getShapeWidth shape =
    match shape with
    | Rectangle(width = w) -> w
    | Circle(radius = r) -> 2. * r
    | Prism(width = w) -> w

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

Разтекание размеченных объединений

В языке F # размеченные объединения часто используются в моделировании доменов для упаковки одного типа. Также можно легко извлечь базовое значение с помощью сопоставления шаблонов. Не нужно использовать выражение соответствия для одного варианта:

let ([UnionCaseIdentifier] [values]) = [UnionValue]

Следующий пример демонстрирует это:

type ShaderProgram = | ShaderProgram of id:int

let someFunctionUsingShaderProgram shaderProgram =
    let (ShaderProgram id) = shaderProgram
    // Use the unwrapped value
    ...

Сопоставление шаблонов также разрешено непосредственно в параметрах функции, поэтому можно разворачивать один случай:

let someFunctionUsingShaderProgram (ShaderProgram id) =
    // Use the unwrapped value
    ...

Размеченные объединения структуры

Можно также представить размеченные объединения как структуры. Это делается с помощью [<Struct>] атрибута.

[<Struct>]
type SingleCase = Case of string

[<Struct>]
type Multicase =
    | Case1 of Case1 : string
    | Case2 of Case2 : int
    | Case3 of Case3 : double

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

  1. Они копируются как типы значений и имеют семантику типов значений.
  2. Нельзя использовать определение рекурсивного типа с многовариантным размеченное объединением.
  3. Необходимо указать уникальные имена вариантов для размеченного объединения с многовариантными структурами.

Использование размеченных объединений вместо иерархий объектов

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

type Shape =
  // The value here is the radius.
| Circle of float
  // The value here is the side length.
| EquilateralTriangle of double
  // The value here is the side length.
| Square of double
  // The values here are the height and width.
| Rectangle of double * double

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

let pi = 3.141592654

let area myShape =
    match myShape with
    | Circle radius -> pi * radius * radius
    | EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
    | Square s -> s * s
    | Rectangle (h, w) -> h * w

let radius = 15.0
let myCircle = Circle(radius)
printfn "Area of circle that has radius %f: %f" radius (area myCircle)

let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)

let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)

Вывод выглядит следующим образом.

Area of circle that has radius 15.000000: 706.858347
Area of square that has side 10.000000: 100.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000

Использование размеченных объединений для структур данных дерева

Размеченные объединения могут быть рекурсивными, что означает, что объединение может включаться в тип одного или нескольких вариантов. Рекурсивные размеченные объединения можно использовать для создания древовидных структур, которые используются для моделирования выражений в языках программирования. В следующем коде рекурсивное размеченное объединение используется для создания структуры данных в двоичном дереве. Объединение состоит из двух вариантов: Node , которые представляют собой узел с целочисленным значением, а также левые и правые поддеревья, и Tip , который завершает дерево.

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

В предыдущем коде resultSumTree имеет значение 10. На следующем рисунке показана древовидная структура для myTree .

Схема, показывающая древовидную структуру для Митри.

Размеченные объединения хорошо работают, если узлы в дереве являются разнородными. В следующем коде тип Expression представляет дерево абстрактного синтаксиса выражения в простом языке программирования, который поддерживает сложение и умножение чисел и переменных. Некоторые варианты объединения не являются рекурсивными и представляют либо числа ( Number ), либо переменные ( Variable ). Другие варианты являются рекурсивными и представляют операции ( Add и Multiply ), где операнды также являются выражениями. EvaluateФункция использует выражение match для рекурсивной обработки дерева синтаксиса.

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 [ "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

При выполнении этого кода значение result равно 5.

Элементы

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

open System

type IPrintable =
    abstract Print: unit -> unit

type Shape =
    | Circle of float
    | EquilateralTriangle of float
    | Square of float
    | Rectangle of float * float

    member this.Area =
        match this with
        | Circle r -> Math.PI * (r ** 2.0)
        | EquilateralTriangle s -> s * s * sqrt 3.0 / 4.0
        | Square s -> s * s
        | Rectangle(l, w) -> l * w

    interface IPrintable with
        member this.Print () =
            match this with
            | Circle r -> printfn $"Circle with radius %f{r}"
            | EquilateralTriangle s -> printfn $"Equilateral Triangle of side %f{s}"
            | Square s -> printfn $"Square with side %f{s}"
            | Rectangle(l, w) -> printfn $"Rectangle with length %f{l} and width %f{w}"

Общие атрибуты

В размеченных объединениях обычно встречаются следующие атрибуты:

  • [<RequireQualifiedAccess>]
  • [<NoEquality>]
  • [<NoComparison>]
  • [<Struct>]

См. также