Система типов Scala 3 позволяет конструировать вторичные структуры данных в пространстве типов. Ярким примером таких структур может выступать HList, впоследствии ставший основой реализации кортежей. Кортежи в Scala 3 стали весьма гибким инструментом, позволяющим захватить в упорядоченном виде сведения о разнородных типах. В настоящей заметке мы рассмотрим реализацию структуры "множество типов" на основе кортежей с использованием инструментов Scala 3.  Какие базовые возможности мы хотели бы видеть для такой структуры? В качестве базового представления будем использовать непосредственно Tuple. Нам достаточно определить все требуемые операции, исходя из того, что Set реализован через Tuple. (Здесь *: — конструктор типа следующего по размеру кортежа. В данном случае создаётся кортеж из одного элемента.) Базовая операция для множеств, определённых конструктивно. Здесь мы рекурсивно разбираем кортеж и проверяем что тип, находящийся в начале кортежа, равен искомому. Если равен — возвращаем тип true. В противном случае, продолжаем рекурсивно разбирать кортеж. Для красоты введём синоним типа, который удобно использовать в инфиксной записи: Объединение множеств заключается в дописывании к одному множеству элементов из другого, которые не входят в исходное множество. Т.к. мы уже определили BelongsTo, то реализация заключается в рекурсивном разборе одного множества с проверкой принадлежности на каждом шаге. Обращаем внимание на то, что сложность этого алгоритма, как и большинства последующих, равна O(N^2). Причём эти вычисления производятся на этапе компиляции. Для красоты вводим сокращённый тип Пересечение строится полностью аналогично. Только начинаем сборку с пустого множества и добавляем элементы, входящие в каждое из множеств. Полностью аналогично конструируем разность множеств. Симметричную разность или операцию XOR мы можем построить, пользуясь имеющимися определениями: Часто важно проверить какое-то свойство на всех элементах множества. Для этого можно использовать квантор всеобщности: Здесь P[_] — функция, определённая в пространстве типов. Принимает на вход один из типов, входящих в множество, и возвращает тип true или false. В реализации мы используем тип &&, определённый в import scala.compiletime.ops.boolean.&&. Аналогично можно определить и квантор существования. Но можно также воспользоваться свойством, связывающим квантор существования с квантором всеобщности: Здесь мы используем type-lambda  — функция в пространстве типов, принимающая аргумент, тип e, и возвращающая тип, находящийся справа от стрелки. Вхождение одного множества в другое можно определить как проверку, что каждый элемент принадлежит второму множеству:  В свою очередь, равенство множеств определяется как вхождение одного в другое и наоборот. Для некоторых операций хотелось бы иметь универсальный инструмент, перебирающий элементы множества. По-видимому, удобным функциональным аналогом цикла является операция fold. Такая операция использует моноид над типами, то есть начальное значение и операцию Combine, объединяющую два элемента. Пользуясь этой парой, мы можем определить foldLeft и foldRight: Здесь мы также используем ограничитель типа моноида Res, чтобы на выходе получить предсказуемый тип результата. Пользуясь введёнными операциями свёртки fold мы можем легко посчитать верхнюю и нижнюю грань всех входящих типов: Здесь в качестве начальной верхней грани берём Nothing, а в качестве операции объединения — |. А для нижней грани, начинаем с Any и объединяем с помощью &. Нам может быть интересно, что некоторое значение (кортеж) содержит значения всех типов из множества. Либо, дополнительно, что все типы множества имеют какое-то значение в переданном кортеже. Такие проверки можно выполнить с помощью implicit'ов, предоставляющих "свидетельства" (evidence) требуемого свойства. Такое свидетельство предоставляется компилятором автоматически, если потребовать Equal[A, B] =:= true. Мы осуществляем такую проверку в два приёма. Вначале конструируем объект, захватывающий тип требуемого множества , а затем у этого объекта реализуем метод apply, который уже принимает тип того значения, которое мы передаём. И, конечно же, в этот момент мы запрашиваем свидетельство того, что множества равны. Тип результата известен нам заранее и гарантированно равен true. Если бы компилятор не смог предоставить свидетельство, то была бы ошибка компиляции, а не false на этапе исполнения. Аналогичным образом можно проверить и свойство IsSubSetOf Либо можно проверить любое свойство над множествами: При использовании других представлений множеств типов, мы можем также определить отрицание множества через Universum: Для Tuple'а, однако, такое определение невозможно, т.к. мы точно знаем все входящие типы, а Universum, содержит неограниченное количество типов. В настоящей заметке рассмотрена реализация инструмента, который может оказаться полезным для некоторых задач, — множества в пространстве типов. В качестве нижележащего представления используется Tuple, а основным инструментом обработки служат match-type'ы. Библиотека опубликована на github'е. Сложность большинства алгоритмов оказывается O(N^2). По-видимому, не стоит использовать эту библиотеку для обработки большого количества типов. Scala архитектор