Поделиться через


Размеченные объединения (F#)

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

type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...]
   ...

Заметки

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

Тип 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."

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

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

Часто размеченное объединение можно использовать в качестве более простой альтернативы небольшой иерархии объектов.Например, следующее размеченное объединение может использоваться вместо базового класса 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.

Структура дерева 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.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

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

См. также

Другие ресурсы

Справочник по языку F#