Важно

  •  

Saturday, September 13, 2025

Mistake: Discriminated Union where not included to C++ (English)



История языка Java служит прекрасной иллюстрацией того, как выбор той или иной фундаментальной структуры влияет на развитие языка на десятилетия вперёд.

Изначально, в 1996 году, язык Java был построен исключительно вокруг объектно-ориентированной парадигмы. В её основе лежит идея полиморфизма через наследование. Это, по сути, реализация идеи произведения. Класс (который является аналогом struct или декартового произведения) может иметь несколько полей и несколько методов. Когда мы создаём иерархию наследования, дочерний класс наследует все поля и методы родительского, то есть он является "произведением" старых и новых свойств. Это мощный, но открытый и расширяемый подход: любой может создать новый класс, унаследовав от вашего.

Однако существует и другой путь к полиморфизму, основанный на идее копроизведения (прямой суммы). В этом подходе мы заранее определяем закрытый, конечный набор возможных типов, которыми может быть наш объект. Вместо того чтобы спрашивать "что ты можешь делать?" (как в ООП), мы спрашиваем "кто ты?".

Спустя почти четверть века, в 2020 году, в Java 14 были введены sealed classes (запечатанные классы). Эта конструкция позволяет программисту объявить класс и явно перечислить '''всех''' его возможных потомков. Например, можно объявить sealed class Shape permits Circle, Square, Triangle и тем самым заявить, что фигура в нашей программе может быть только кругом, квадратом или треугольником, и ничем иным.

Это и есть, по своей сути, введение в язык размеченного объединения (tagged union). Оно позволяет компилятору проверять полноту обработки случаев (например, в операторе switch), гарантируя, что вы не забыли рассмотреть один из возможных типов. Это делает код более безопасным и предсказуемым.

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

В теории категорий, которая изучает математические структуры на самом высоком уровне абстракции, произведение объектов A и B — это такой объект P вместе с двумя "проекциями" на A и B, который универсальным образом решает задачу "объединения информации" из A и B.

Эта глубокая идея находит прямое отражение в программировании. Когда вы определяете структуру или запись (record/struct), содержащую несколько полей (например, struct Point { int x; int y; }), вы, по сути, создаёте декартово произведение типов `int` и `int`. Кортежи (tuples) в таких языках, как Python, также являются прямым воплощением этой идеи.

Замечание:: Дуальная конструкция: Прямая сумма.

В математике у многих важных конструкций есть "зеркальный двойник", или дуальная конструкция. Для декартова произведения такой конструкцией является дизъюнктное (несвязное) объединение, или прямая сумма, обозначаемая A \sqcup B (или A+B).

Если декартово произведение объединяет элементы из A и B в пары, то прямая сумма объединяет их в одно множество, но при этом "помечает" каждый элемент, чтобы мы знали, откуда он пришёл. Например, элемент a \in A попадает в сумму как пара (a, 0), а элемент b \in B — как (b, 1). Это гарантирует, что даже если множества A и B пересекались, в их прямой сумме их копии будут разделены.

Эта конструкция также является универсальной и известна в теории категорий как копроизведение. Её проявления в программировании известны как размеченные объединения (tagged unions) или типы-суммы (sum types). Они позволяют определить тип данных, который может принимать значение одного из нескольких других типов (например, переменная может быть либо целым числом, либо строкой).

Такие конструкции можно реализовать даже в таких языках, как C, используя комбинацию union и struct. union позволяет хранить в одной области памяти данные разных типов, а поле-«тег» в окружающей его struct указывает, какой именно тип данных там находится в данный момент.



The history of the Java language serves as an excellent illustration of how the choice of a fundamental structure can influence the development of a language for decades to come.

Originally, in 1996, the Java language was built entirely around the object-oriented paradigm. At its core lies the idea of polymorphism through inheritance. This is, essentially, an implementation of the idea of a product. A class (which is analogous to a struct or Cartesian product) may have multiple fields and multiple methods. When we create an inheritance hierarchy, a child class inherits all the fields and methods of its parent, meaning it is a "product" of old and new properties. This is a powerful, open, and extensible approach: anyone can create a new class by inheriting from yours.

However, there is another path to polymorphism, based on the idea of a coproduct (direct sum). In this approach, we define in advance a closed, finite set of possible types that our object may be. Instead of asking "what can you do?" (as in OOP), we ask "who are you?".

Nearly a quarter of a century later, in 2020, Java 14 introduced sealed classes. This construct allows a programmer to declare a class and explicitly list '''all''' of its possible subclasses. For example, one may declare sealed class Shape permits Circle, Square, Triangle, thereby stating that a shape in our program can only be a circle, a square, or a triangle—and nothing else.

This is, in essence, the introduction of a tagged union into the language. It allows the compiler to check the exhaustiveness of case handling (for example, in a switch statement), ensuring that you haven’t forgotten to account for one of the possible types. This makes code safer and more predictable.

This story shows that both mathematical constructions — product and coproduct — are fundamental and do not replace one another. They represent two different, dual approaches to modeling the world. Classical OOP chose one path, while modern programming trends highlight the importance of the other, based on the idea of the direct sum.

In category theory, which studies mathematical structures at the highest level of abstraction, the product of objects A and B is an object P together with two "projections" onto A and B, which universally solves the problem of "combining information" from A and B.

This deep idea finds direct expression in programming. When you define a structure or record containing multiple fields (for example, struct Point { int x; int y; }), you are essentially creating the Cartesian product of the types `int` and `int`. Tuples in languages like Python are also a direct embodiment of this idea.

'''Note:''': The dual construct: Direct sum.

In mathematics, many important constructions have a "mirror twin," or dual construct. For the Cartesian product, this construct is the disjoint union, or direct sum, denoted A \sqcup B (or A+B).

If the Cartesian product combines elements from A and B into pairs, the direct sum combines them into a single set, but "tags" each element so we know where it came from. For example, an element a \in A appears in the sum as the pair (a, 0), and an element b \in B as (b, 1). This guarantees that even if sets A and B overlapped, their copies in the direct sum remain distinct.

This construct is also universal and is known in category theory as a coproduct. Its manifestations in programming are known as tagged unions or sum types. They allow you to define a data type that may take on the value of one of several other types (for example, a variable may be either an integer or a string).

Such constructs can even be implemented in languages like C, using a combination of union and struct. A union allows different types of data to be stored in the same memory area, while a "tag" field in the surrounding struct indicates which type of data is currently stored there.

No comments:

Post a Comment