Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, Sinclair, Вы писали:
S>> ImmutableValue(T && value) : value_(value) {}
S>>Хм. А там точно будет ссылка на оригинал, а не дубликат?
V>Там будет и не ссылка, и не дубликат.
V>Тогда мы храним ссылку, а не значение, т.е. теряем контроль над другими потенциальными ссылками на это значение.
V>Я ж не просто так храню именно значение. ))
S>>Покажите мне пример кода, в котором вы изготавливаете из мутабельного объекта иммутабельный, и продемонстрируйте, что вы после этого не можете этот иммутабельный изменить.
V>V>template<typename T>
V>class ImmutableValue {
V> const T value_;
V>public:
V> ImmutableValue(T && value) : value_(move(value)) {}
V> const T & value() const { return value_; }
V>};
V>int main()
V>{
V> using namespace std;
V> typedef ImmutableValue<string> istring;
V> string s1 = "it is a long enough string";
V> istring s2 = move(s1);
V> assert(s2.value() == "it is a long enough string");
V> assert(s1 == "");
V> s1 = "42";
V> assert(s2.value() == "it is a long enough string");
V> assert(s1 == "42");
V> return 0;
V>}
V>
Понятно. Вижу, вы исправили ошибку — теперь у вас в коде 2 move. Исходный код, который вы привели, падал на
assert(s1 == "").
V>Здесь "it is a long enough string/0" выделяется в динамической памяти, затем через std::move перемещается в иммутабельную строку s2, при этом строка s1 теряет своё содержимое.
V>Далее мы изменяем значение строки s1, в то время как зачение s2 не изменяется.
V>Изменить законными способами значение переменной s2 нельзя;
Осталось понять, что изменится, если мы поубираем ключевые слова const из вашего шаблонного кода. Перестанет ли isting быть иммутабельным?
V>V> istring s3 = s2; // создание копии строки
V> assert(s3.value() == "it is a long enough string");
V> assert(s2.value().data() != s3.value().data()); // именно копии, по разным адресам
V> s2 = s3; // ошибка компиляции: operator=(const ImmutableValue<std::string> &) удалён
V>
V>Последняя строка тоже важна для понимания — получив однажды ссылку на ImmutableValue<std::string> я могу быть уверен, что переменную не затрут.
Вот этот фрагмент непонятен. Зачем вы производите
копии иммутабельного объекта? Он же гарантированно иммутабельный, это как раз и даёт ссылочную прозрачность.
V>>>Далее в своём коде используешь шаблонный ImmutableMap.
S>>Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
S>>Как вы реализуете метод add(const TKey & key, const TValue & value)?
V>Никак.
V>Для иммутабельных типов это будут внешние ф-ии и операторы, разумеется.
V>Просто в дотнете сплошные методы. ))
Методы гораздо удобнее выстраивать в цепочку. Запись вида
list.add(1).add(2).add(42) читать гораздо комфортнее, чем
add(add(add(list, 1), 2), 42))).
Но это дело вкуса. Покажите, как вы будете реализовывать
внешний метод add.
V>Можно.
V>Зря сомневаешься.
Ну, не с первого раза, но, вроде бы, удалось. Правда, ваш тип зачем-то создаёт избыточные копии на ровном месте, ну это ладно. Наверное, можно научить людей избегать передачи по значению, хоть это и неудобно.
V>Это смотря, какова природа/структура и объемы данных.
V>copy-on-write для небольших объемов линейно-расположенных в памяти данных работает лучше иммутабельного графа.
Началось виляние. Узнаю коллегу вдимаса.
V>Но это мы уже ответвились в другую область — в область алгоритмов и структур данных, а они зависят от конкретных задач (или классов задач, бо практически все задачи уже известны, бгг).
Конкретная задача хорошо известна — реализовать иммутабельные структуры так, чтобы они были а) надёжными, б) эффективными.
V>В моём случае не придётся для сценария, скажем, для которого разработали FrozenDictionary в 8-м дотнете.
Да, для этого экзотического сценария можно сделать реализацию эффективнее, чем ImmutableDictionary. Обратите внимание на две вещи:
1. Ваша реализация оказалась уместна только для экзотического частного случая; для более общего частного случая она непригодна.
2. В дотнете как-то обошлись без магии ключевого слова const. Выходит, любые виды неизменности реализуемы и без него. Сюрприз-сюрприз!
V>И странно, что оно не было сделано еще лет 20 назад, когда тема иммутабельных деревьев была модной на слуху.
Сначала удовлетворяются потребности 90% аудитории. Потом — 9%. Потом — 0.9%. Вот теперь дошла очередь и до 0.1%.
V>В C# ключевое слово const у мемберов объявляет аналог статического readonly поля объекта.
V>Тебе нужны были железобетонные гарантии — ну и вот.
Так гарантии по-прежнему обеспечены дизайном класса, а не ключевым словом const.
V>Если же речь об алгоритмах и структурах данных — ну так пиши эти структуры и алгоритмы над ними.
V>Весь механизм для этого есть.
V>Это именно реализация некоторых иммутабельных алгоритмов над некоторыми иммутабельными структурами данных.
V>Collections.Immutable не приносит в язык иммутабельность как таковую, эта либа лишь реализует некоторые структуры и алгоритмы, доказавшие свою полезность в ФП.
По-видимому, вы так и не понимаете, что такое иммутабельность как таковая.
V>Я могу нарушать гарантии интерфейсов, например, реализовывать тип как мутабельный и возвращать this из методов.
Интерфейсов — да. Классов — нет.
S>>Вот мой метод:
S>>S>>public void RegisterState(ImmutableDictionary<string, int> state)
S>>{
S>> _visited[state] = true;
S>>}
S>>
S>>Попробуйте "сломать" его, передав в него мутабельный state.
V>У тебя ошибка, должно быть так:
Нет никакой ошибки. state — immutable, _visited — mutable.
V>И это у тебя оно гарантируется из-за использования конкретного типа ImmutableDictionary<,>, а если сделать так:
V>V>public void RegisterState<TDict, TKey, TValue>(TDict state)
V> where TDict : IImmutableDictionary<TKey, TValue> {...}
V>
V>то прощай гарантии, можно начинать хулиганить. ))
Ну, так поэтому я и не стал писать так, чтобы вы начинали хулиганить.
V>В плюсах, напротив, можно определить некий концепт ImmutableMap и юзать так:
V>V>template<ImmutableMap TDict>
V>void RegisterState(TDict state) {
V> visited_ = merge(visited_, state, true);
V>}
V>
V>Где концепт ImmutableMap требует, например, быть инстансом шаблонного типа ImmutableMapWrapper, которому в кач-ве аргумента подали тип, удовлетворяющий публичному интерфейсу std::map.
И чем это будет отличаться от конкретного
ImmutableMapWrapper<std::map>?
V>Что касается ключевой фишки иммутабельных списков/графов, т.е. что касается шаренья узлов между инстансами, этого можно достичь или через счётчики ссылок, или через техники навроде региональной памяти, чтобы не возиться со счётчиками.
Эффективность упадёт на дно.