Tagged union - Tagged union

В Информатика, а помеченный союз, также называемый вариант, вариантная запись, тип выбора, размеченный союз, несвязный союз, тип суммы или же сопродукт, это структура данных используется для хранения значения, которое может принимать несколько разных, но фиксированных типов. Одновременно может использоваться только один из типов, а тег поле явно указывает, какой из них используется. Его можно рассматривать как тип, имеющий несколько «случаев», каждый из которых должен обрабатываться правильно при манипулировании этим типом. Это критически важно при определении рекурсивных типов данных, в которых некоторый компонент значения может иметь тот же тип, что и само значение, например, при определении типа для представления деревьев, где необходимо различать многоузловые поддеревья и листья. Как обычный союзы, помеченные объединения могут сэкономить пространство для хранения, перекрывая области хранения для каждого типа, поскольку одновременно используется только одно.

Описание

Отмеченные союзы наиболее важны в функциональные языки Такие как ML и Haskell, где они называются типы данных (видеть алгебраический тип данных ), и компилятор может проверить, что все случаи помеченного объединения всегда обрабатываются, избегая многих типов ошибок. Однако их можно построить практически в любом язык, и намного безопаснее немаркированных союзов, часто называемых просто союзами, которые похожи, но не отслеживают явно, какой член союза используется в настоящее время.

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

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

В теория типов, помеченное объединение называется тип суммы. Типы сумм - это двойной из виды продукции. Обозначения различаются, но обычно тип суммы поставляется с двумя формами введения (инъекции ) и . Форма исключения - это анализ случая, известный как сопоставление с образцом в ML-стиль языки программирования: если имеет тип и и иметь тип при предположениях и соответственно, то член имеет тип . Тип суммы соответствует интуиционистский логическая дизъюнкция под Переписка Карри – Ховарда.

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

Многие методы программирования и структуры данных, включая веревка, ленивая оценка, иерархия классов (Смотри ниже), арифметика произвольной точности, CDR кодирование, то косвенный бит и другие виды помеченные указатели и т. д. обычно реализуются с использованием некоего помеченного объединения.

Тегированный союз можно рассматривать как простейший вид самоописывающий формат данных. Тег помеченного союза можно рассматривать как простейший вид метаданные.

Преимущества и недостатки

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

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

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

Иногда немаркированные союзы используются для выполнения преобразований на битовом уровне между типами, которые в C ++ называются преобразованием типов. Союзы с тегами не предназначены для этой цели; обычно новое значение назначается при изменении тега.

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

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

где "value" и "err" - конструкторы типа union, А и B допустимые типы результатов и E - это тип условий ошибки. С другой стороны, ту же монаду можно описать как возвращаться и две дополнительные функции, fmap и присоединиться:

Примеры

Скажем, мы хотели построить двоичное дерево целых чисел. В ML мы бы сделали это, создав такой тип данных:

тип данных дерево = Лист              | Узел из (int * дерево * дерево)

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

Узел(5, Узел(1, Лист, Лист), Узел(3, Лист, Узел(4, Лист, Лист)))

что соответствует этому дереву:

Дерево, созданное вышеуказанными конструкторами

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

весело countNodes(Лист) = 0  | countNodes(Узел(int, оставили, верно)) =      1 + countNodes(оставили) + countNodes(верно)

Хронология языковой поддержки

1960-е

В АЛГОЛ 68, отмеченные союзы называются объединенные режимы, тег неявный, а дело конструкция используется для определения того, какое поле помечено:

Режим узел = союз (настоящий, int, компл, нить);

Пример использования для союз дело из узел:

узел n: = «1234»;дело п в  (настоящий r): print (("real:", r)), (int i): print (("int:", i)), (компл c): print (("Compl:", c)), (нить s): print (("строка:", s)) из         print (("?:", n))esac

1970-е и 1980-е годы

Хотя в основном только функциональные языки Такие как ML и Haskell (с 1990-х годов) отводят центральную роль помеченным союзам и имеют право проверять, обрабатываются ли все дела, другие языки также поддерживают помеченные союзы. Однако на практике они могут быть менее эффективными в нефункциональных языках из-за оптимизации, включенной компиляторами функционального языка, которые могут исключить явные проверки тегов и избегать явного хранения тегов.[нужна цитата ]

Паскаль, Ада, и Модула-2 позвони им вариантные записи (формально размеченный тип в Ada) и требуют, чтобы поле тега создавалось вручную и значения тегов указывались, как в этом примере Pascal:

тип форма = (квадрат, прямоугольник, круг);     форма = записывать                Centerx : целое число;                центр : целое число;                дело своего рода : форма из                   квадрат : (сторона : целое число);                   прямоугольник : (длина, высота : целое число);                   круг : (радиус : целое число);	      конец;

и этот эквивалент Ады:

тип Форма_вид является (Квадрат, Прямоугольник, Круг);тип Форма (вид : Форма_вид) является записывать   Center_X : Целое число;   Center_Y : Целое число;   дело вид является      когда Квадрат =>         Сторона : Целое число;      когда Прямоугольник =>         Длина, Высота : Целое число;      когда Круг =>         Радиус : Целое число;   конец дело;конец записи;- Любая попытка доступа к члену, существование которого зависит- от определенного значения дискриминанта, а- дискриминант не ожидаемый, возникает ошибка.

В C и C ++, помеченное объединение может быть создано из немаркированных объединений с использованием строгой дисциплины доступа, при которой тег всегда проверяется:

перечислить Форма Вид { Квадрат, Прямоугольник, Круг };структура Форма {    int Centerx;    int центр;    перечислить Форма Вид своего рода;    союз {        структура { int сторона; };           /* Квадрат */        структура { int длина, высота; }; / * Прямоугольник * /        структура { int радиус; };         /* Круг */    };};int getSquareSide(структура Форма* s) {    утверждать(s->своего рода == Квадрат);    возвращаться s->сторона;}пустота setSquareSide(структура Форма* s, int сторона) {    s->своего рода = Квадрат;    s->сторона = сторона;}/* и так далее */

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

C и C ++ также имеют языковую поддержку для одного конкретного помеченного объединения: возможно-null указатель. Это можно сравнить с вариант введите ML или Может быть введите в Haskell, и его можно рассматривать как помеченный указатель: помеченное объединение (с закодированным тегом) двух типов:

  • Действительные указатели,
  • А нулевой указатель тип только с одним значением, ноль, что указывает на исключительное состояние.

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

2000-е

Один продвинутый диалект C называется Циклон имеет обширную встроенную поддержку помеченных объединений.[1]

Типы перечисления в Ржавчина, Haxe и Быстрый языки также работают как помеченные союзы.

Библиотека вариантов из Способствовать росту продемонстрировал, что можно реализовать безопасное помеченное объединение в виде библиотеки на C ++, доступной с помощью объектов функций.

структура отображать : способствовать росту::static_visitor<пустота>{    пустота оператор()(int я)    {        стандартное::cout << "Это целое число со значением" << я << стандартное::конец;    }    пустота оператор()(const стандартное::нить& s)    {        стандартное::cout << «Это строка со значением» << s << стандартное::конец;    }};способствовать росту::вариант<int, стандартное::нить> v = 42;способствовать росту::apply_visitor(отображать(), v);способствовать росту::вариант<int, стандартное::нить> v = "Привет, мир";способствовать росту::apply_visitor(отображать(), v);

Scala имеет классы случаев:

запечатанный Абстрактные учебный класс Дереводело объект Лист расширяет Дереводело учебный класс Узел(ценить: Int, оставили: Дерево, верно: Дерево) расширяет Деревовал дерево = Узел(5, Узел(1, Лист, Лист), Узел(3, Лист, Узел(4, Лист, Лист)))

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

дерево матч {  дело Узел(Икс, _, _) => println("значение узла верхнего уровня:" + Икс)  дело Лист          => println("узел верхнего уровня - лист")}

Классы кейсов Scala также допускают повторное использование через подтипы:

запечатанный Абстрактные учебный класс Форма(centerX: Int, centerY: Int)дело учебный класс Квадрат(сторона: Int, centerX: Int, centerY: Int) расширяет Форма(centerX, centerY)дело учебный класс Прямоугольник(длина: Int, высота: Int, centerX: Int, centerY: Int) расширяет Форма(centerX, centerY)дело учебный класс Круг(радиус: Int, centerX: Int, centerY: Int) расширяет Форма(centerX, centerY)

F # дискриминировал союзы:

тип Дерево =  | Лист  | Узел из ценить: int * оставили: Дерево * верно: Деревопозволять дерево = Узел(5, Узел(1, Лист, Лист), Узел(3, Лист, Узел(4, Лист, Лист)))

Поскольку определенные случаи являются исчерпывающими, компилятор может проверить, что все случаи обрабатываются в соответствии с шаблоном:

матч дерево с| Узел (Икс, _, _) -> printfn "значение узла верхнего уровня:% i" Икс| Лист           -> printfn "узел верхнего уровня - лист"

Haxe перечисления также работают как помеченные объединения[2]:

перечислить Цвет {  красный;  Зеленый;  Синий;  RGB(р:Int, грамм:Int, б:Int);}

Их можно сопоставить с помощью выражения switch:

выключатель (цвет) {  дело красный: след(«Цвет был красный»);  дело Зеленый: след(«Цвет был зеленый»);  дело Синий: след(«Цвет был синий»);  дело RGB(р, грамм, б): след(«Красный цвет имеет значение» +р);}

Ним есть варианты объекта[3] аналогично объявлению в Паскале и Аде:

тип  Форма Вид = перечислить    skSquare, skRectangle, skCircle  Форма = объект    centerX, centerY: int    дело своего рода: Форма Вид    из skSquare:      сторона: int    из skRectangle:      длина, высота: int    из skCircle:      радиус: int

Макросы может использоваться для имитации сопоставления с образцом или для создания синтаксического сахара для объявления вариантов объекта, как здесь показано как реализованное пакетом Пэтти:

импорт Пэттиproc `~`[А](а: А): ссылка А =  новый(результат)  результат[] = авариант Список[А]:  Ноль  Минусы(Икс: А, хз: ссылка Список[А])proc listHelper[А](хз: seq[А]): Список[А] =  если хз.len == 0: Ноль[А]()  еще: Минусы(хз[0], ~listHelper(хз[1 .. хз.высоко]))proc список[А](хз: varargs[А]): Список[А] = listHelper(@хз)proc сумма(хз: Список[int]): int = (блокировать:  матч хз:    Ноль: 0    Минусы(у, ys): у + сумма(ys[]))эхо сумма(список(1, 2, 3, 4, 5))

2010-е

В Язык ржавчины имеет обширную поддержку помеченных объединений, называемых перечислениями.[4] Например:

перечислить Дерево{Лист,Узел(i64,Коробка<Дерево>,Коробка<Дерево>)}

Это также позволяет подбирать соединения:

позволятьдерево=Дерево::Узел(2,Коробка::новый(Дерево::Узел(0,Коробка::новый(Дерево::Лист),Коробка::новый(Дерево::Лист))),Коробка::новый(Дерево::Узел(3,Коробка::новый(Дерево::Лист),Коробка::новый(Дерево::Узел(4,Коробка::новый(Дерево::Лист),Коробка::новый(Дерево::Лист))))));fn add_values(дерево: Дерево)-> i64 {матчдерево{Дерево::Узел(v,а,б)=>v+add_values(*а)+add_values(*б),Дерево::Лист=>0}}assert_eq!(add_values(дерево),9);

Модель обработки ошибок Rust во многом полагается на эти помеченные объединения, особенно Вариант тип, который либо Никто или же Некоторые (T), а Результат тип, который либо Хорошо (T) или же Err (E).[5]

С Машинопись также возможно создание помеченных союзов. Например:

интерфейс Лист { тип: "лист"; ценить: нить; }интерфейс Узел { тип: "узел"; оставили: Дерево; верно: Дерево; }тип Дерево = Лист | Узелфункция посещение(дерево: Дерево) {    выключатель (дерево.тип) {        дело "лист":            консоль.бревно(дерево.ценить)            перемена        дело "узел":            посещение(дерево.оставили)            посещение(дерево.верно)            перемена     } }

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

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

Тем не менее, иерархия классов включает в себя истинные полиморфизм подтипа; его можно расширить, создав дополнительные подклассы того же базового типа, которые нельзя правильно обработать в рамках модели тегов / отправки. Следовательно, обычно невозможно провести анализ случая или отправить по «тегу» подобъекта, как это было бы для помеченных объединений. Некоторые языки, такие как Scala позволяют «запечатать» базовые классы и объединять помеченные объединения с запечатанными базовыми классами.

Смотрите также

Рекомендации

  1. ^ http://cyclone.thelanguage.org/wiki/Tagged%20Unions
  2. ^ «Использование Enums - Haxe - кроссплатформенный инструментарий». Фонд Haxe.
  3. ^ "Nim Manual". nim-lang.org. Получено 2020-01-23.
  4. ^ "Язык программирования Rust". Mozilla.
  5. ^ «Пример ржавчины». Mozilla.

внешняя ссылка

  • boost :: option является типобезопасным дискриминированным объединением C ++
  • std.variant это реализация вариантного типа в D 2.0