Есть и другие способы сделать это, но они могут не иметь желаемых свойств.
Предположим, вы хотите представить неизменное дерево значений, построенное из листьев вверх.Это довольно просто.У вас может быть конструктор узла, который принимает значение, и список дочерних узлов.Это делает довольно простым создание новых деревьев из старых деревьев, и они гарантированно являются ациклическими.
Теперь предположим, что вы хотите представить неизменный ориентированный граф значений.Теперь у вас есть проблема, что узлы могут иметь циклы;не может быть «листа» для построения графа.Решение состоит в том, чтобы отказаться от принципа, согласно которому узел знает своих соседей.Вы можете представить неизменный граф, создав неизменный набор узлов и неизменный список ребер.Чтобы добавить узел в неизменный граф, вы строите новый граф с этим узлом, добавленным в набор узлов.Аналогично для добавления ребра;Вы строите новый список ребер.Теперь тот факт, что в топологии графа есть циклы, не имеет значения;ни у одного объекта нет цикла в том, на какие объекты он ссылается.
Не зная больше о вашей реальной проблемной области, трудно сказать, какая неизменная структура данных будет работать для вашего приложения.Можете ли вы рассказать нам больше о том, что вы пытаетесь сделать?
Я пытаюсь смоделировать коллекцию типов.Каждый тип имеет имя и несколько атрибутов.Каждый атрибут имеет имя и тип.Существует несколько взаимно рекурсивных типов, и именно здесь возникла эта проблема.
Ну и дела, ты должен был сказать об этом в первую очередь.Если есть одна вещь, о которой я знаю, это анализ типов.Очевидно, что компилятор должен уметь обрабатывать все виды ситуаций типа сумасшедших, включая типы с циклическими базовыми классами, циклы с внутренними типами, аргументы типов, ограничения типов и т.проблемы в основном из-за того, что объекты «поставлены» в своей неизменности.То есть, когда мы впервые создаем набор типов, каждый объект типа знает свое имя и свою позицию в исходном коде (или метаданных).Имя тогда становится неизменным.Затем мы определяем базовые типы и проверяем их на наличие циклов;базовые типы становятся неизменяемыми.Затем мы проверяем ограничения типа ... затем мы разрешаем атрибуты ... и так далее, пока все не будет проанализировано.
Я рассмотрел другие способы сделать это.Например, мы могли бы использовать ту же методику, которую я только что предложил для графов: создать неизменный объект, называемый, скажем, «компиляция», к которому можно добавлять типы, создавая тем самым новые неизменяемые компиляции.Компиляция может отслеживать «ребра» между типом и его базовым типом в неизменяемой хэш-карте, а затем может проверять результирующий граф на циклы.Недостатком является то, что тип не знает своего базового типа;Вы должны спросить компиляцию, каков базовый тип типа.
Вы можете сделать то же самое здесь.Вы можете иметь класс «typeset», который содержит набор неизменяемых типов, и мультикарту от типа к набору неизменяемых атрибутов.Вы можете создать набор типов и набор атрибутов так, как вам нравится;то, что меняется, это карта, а не тип.
Недостатком является то, что вы больше не спрашиваете у type его атрибуты;Вы спрашиваете набор для атрибутов типа.Это может не соответствовать вашим потребностям, если вам необходимо передавать типы независимо от набора.