Информация об изменениях

Сообщение Re[91]: Когда это наконец станет defined behavior? от 05.09.2023 11:16

Изменено 05.09.2023 11:28 ·

Re[91]: Когда это наконец станет defined behavior?
Здравствуйте, vdimas, Вы писали:

v>>> ·>покажи пример кода как обеспечить иммутабельность через view мутабельного объекта.

v>>> Легко — достаточно обеспечить отсутствие протекание ссылки на мутабельный объект.
v>>> (например, создавать приватный мутабельный объект в конструкторе view)
V>·>Не очень понял что ты имеешь в виду, продемонстрируй кодом.
V>Берёшь достаточно сложный мутабельный объект и прикручиваешь сверху иммутабельный view.
Ясно. Просто ты опять плаваешь в терминологии и делаешь странные обобщения. Тут у тебя не "иммутабельность может быть св-вом объекта, а может быть адаптером-view", а "иммутабельность может быть обёрткой мутабельного типа", тупо инкапсуляция. В этом принципиальное отличие от константности, ведь константность действительно можно обеспечить "через view мутабельного объекта" — т.е. во view оборачивается инстанс типа (т.е. объект).

И вот тут я опять подозреваю, что ты нарочно флейм разводишь и цирк устраиваешь. Я попросил продемонстрировать "пример кода как обеспечить иммутабельность", а ты мне вываливаешь ReadOnlyDictionary, как нарочно мешаешь всё в кучу: кручу, верчу, запутать хочу.
Или ты вообще невдупляешь что такое иммутабельность. Если так, то почитай доку .NET чем отличаются IImmutableDictionary и IReadOnlyDictionary.

v>>> Достаточно просто — объявляешь данные как const и они автоматом становятся иммутабельными.

V>·>А дальше что? В лучшем случае ты их можешь такими сделать в пределах локальной переменной или поля класса.
V>Для глобальных переменных аналогично.
А, ну да, забыл про это. Ибо глобальные переменные — зло.

V>·>В другом скопе он уже теряет иммутабельность.

V>Если речь про эти же самые данные — то иммутабельность не теряется.
Теряется с т.з. компилятора. Иммутабельность данных по конст-ссылкам никак не гарантируется компилятором.

V>Но можно получить мутабельную копию данных (в общем случае, если не запрещать конструктор копирования), где изменения в копии не будут влиять на исходные данные.

Дело не в этом. Дело в том, что все те ништяки, которые имеются у иммутабельных типов — исчезают, т.к. имея даже "const X" нельзя быть уверенным — иммутабельный он или нет. И совсем плохо, если у тебя "const X*" — тут вообще беда.

V>·>По значению это и есть defensive copy.

V>Э, нет. ))
V>Это в джаве такая техника, связанная с тем, что все объекты имеют ссылочную семантику.
Нет. Вот это твоё "data.ToDictionary(item => item.key, item => item.value)" — и есть defensive copy — ты копируешь данные внтурь объекта и не даёшь к ним мутирующий доступ, инкапсуляция обычная. А твоё "Wrap(Dictionary<TKey, TValue> dict) => new(dict)" — это бага. Оно тоже копию должно создавать, иначе это вовсе не иммутабельный тип как ты обещал выше, а ReadOnly view.

V>Тогда, если объект не предоставляет явного read-only АПИ, необходимо создавать его копию при подаче такого объекта в кач-ве аргументов, чтобы вызываемый код не изменил состояние вызывающего кода.

Только иммутабельность для этого не нужна, для этого достаточно константности. И даже копию делать вовсе не обязательно — можно просто RO-view обёртку для того же объекта.

V>В этом месте я и стебался, что в джаве не всегда даже заморачиваются с такими вещами — банально можно прохлопать или полениться, типа, вызывающий код достоверно не изменяет аргументы... а потом оп-па при очередных изменениях — получи фашист гранату! ))

Не заморачиваются, как правило, для простоты, из-за лени...

V>В дотнете такие вещи обыгрываются автоматически через value-type, где при передаче по-значению копия создаётся автоматом.

В дотнете создают ImmutableList, ReadOnlyCollection, string/StringBuilder и т.п. Иначе получается фигня, как у тебя в плюсах.

V>Второй способ — это передача readonly-ссылки на value-type значение.

Опять же read-only. Мы говорим об иммутабельности же.


v>>> ·>Ответ — да никак.

v>>> Для этого необходимо показать хотя бы минимальный пример этого "никак".
V>·>Пример чего? Пример кода, который никак не написать??!
V>Пример нарушения иммутабельности константного объекта.
Через утечку this в конструкторе, например, ты же сам этот код где-то выше показывал. Или вообще — запустили в конструкторе новый тред и меняем там this когда захочется. А компилятору — пофиг.

v>>> Это лишь один из трюков, достижимых при иммутабельности.

v>>> Так же как шаренье иммутабельных данных без блокировки м/у потоками.
V>·>Угу. А константные объекты даже пошарить нельзя.
V>Константные объекты не можно, а нужно шарить. ))
V>Собсно, зачастую для таких сценариев константные объекты и используются, включая обычные константы.
Но там, где они пошарены — уже нельзя рассчитывать на плюшки иммутабельости.

V>·>Да, но там создаётся иммутабельная копия из данных мутабельного билдера. Как и в шарпах всяческих.

V>Есть еще несколько вариантов:
V>* Данные не пересоздаются в конце работы builder, а меняют своего владельца, сам builder помечает этот факт в данных и, если построение данных продолжится после выдачи иммутабельного объекта, то builder создаст себе новую копию данных для дальнейшего над ними измывательства;
V>* Данные в конце работы builder меняют своего владельца, при этом сам builder обнуляется. Дальнейшее построение либо невозможно, либо ведётся с нуля.
Это уже мелкие оптимизации. Со своими преимуществами. И недостатками (которые ты скромно умалчиваешь).

v>>> Вот после создания иммутабельного объекта, все эти трюки можно использовать.

V>·>С иммутабельным можно. С константным — не всегда, только компилятор гарантии не даёт.
V>Я попросил пример, где компилятор не даёт гарантии.
Я показал выше.

v>>> ·>Как и в Плюсах.

v>>> В плюсах компилятор больно бъёт тебе по пальцам.
V>·>Компилятор ничего про иммутабельность не знает, поэтому по пальцам бить тупо не может. Нет такого понятия в стандарте.
V>Другой термин, бо в стандарте описаны конструкции языка, а не прикладная роль данных.
Ну в яве почему-то не постеснялись написать "другой термин" (нет, не просто термин, а другая семантическая конструкция) в описании рекордов.

V>·>ИЧСХ, ты это и так прекрасно понимаешь (когда, например, ты скромно "забыл" назвать объект типа const SomeObj иммутабельным). Я не понимаю зачем ты тут этот цирк устраиваешь?

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

v>>> ·>Ровно твои слова же: "Если протечек ссылок для мутабельности нет, то автоматом получается иммутабельность". К чему сарказм-то?

v>>> К тому, что компилятор в плюсах отслеживает попытки "протечек" и отказывается компилировать такой код.
V>·>Не отслеживает. Сам же ниже пишешь, например, о сценарии утечки this из конструктора.
V>Это классический побочный эффект.
V>Тоже иногда сильная и нужная весчь, просто не всегда сочетается с иммутабельными сценариями.
Угу. Т.е. не отслеживает. ЧТД.

V>Чтобы сочетать побочные эффекты с иммутабельностью, данные надо сразу описывать как иммутабельные (init-only), т.е. все поля объекта должны быть const и инициализироваться до тела конструктора (в списке инициализации переменных), т.е. до возможности оперировать переменной this.

Или как final-поля в яве.

V>Тогда строгая иммутабельность объекта будет обеспечена даже без ключевого слова const перед переменной его типа. ))

Именно. const для иммутабельности не нужен.

V>Но это крайний случай, который не сильно интересен, потому что не получаем плюшек выразительности/обобщения.

V>Такая возможность "просто есть", но используется редко, бо редко надо совмещать иммутабельность с побочными эффектами.
V>Но зато когда надо — понятно что делать.


v>>> ·>Вопрос в том, как собственно обеспечить отсутствие протечек.

v>>> result[{42, 43}] = "42, 43";
V>·>Здесь формально копиктор struct срабатывает.
V>Здесь сам словарь мутабельный, ключ словаря иммутабельный, данные по ключу перезаписываемы.
V>result[index] возвращает non-const ссылку на значение, по этой ссылке происходит присвоение нового значения.
Тут срабатыват копиктор структуры, как примерно вот так index key{42, 43}; result[key] = "42, 43";. Внутри мапы создаётся копия ключа.

V>·>И здесь формально копия.

V>Формально копия, верно.
V>А на деле никакой копии — дополнительные временные объекты не создаются.
На деле это необязательная оптимизация.

V>·>Но допустим у тебя есть этот самый dict офигенного размера и он тут на стеке вроде как иммутабельный. Как его теперь обработать из другого треда так, чтобы этот самый другой тред был уверен, что ему передают только иммутабельные объекты?

V>Это зависит от взаимного времени жизни — поток не должен пережить время жизни такой переменной (например, создали такую переменную в main и передали программе дальше).
Ну дестркуторы отдельная тема. Деструктор, к сожалению, может поменять состояние даже супер-пупер константно-иммутабельного объекта.

V>Или же такая переменная может быть глобальной/статической, как я и показал в примере.

Проблема в том, что этот же тред потом можно будет внезапно вызвать с мутабельным объектом.

v>>> В данном примере гарантируется, что переменная dict иммутабельная, хотя на этапе построения мы пошагово мутировали значение.

V>·>Это немного враньё. Значение мы мутировали у result, а dict это копия.
V>Формально да.
V>А на деле происходили операции в одной области памяти, т.е. получился классический кейз иммутабельных данных — init-only.
Это оптимизации всё. После jit тоже много чего интересного происходит.

V>В этом примере через формальное якобы создание копии разделяется время жизни мутабельной переменной result и иммутабельной переменной dict, где вторая становится доступной для оперирования строго после того, как переменная result стала недоступной. Хотя, обе переменные ссылаются на одну область памяти.

Это аналогично Dictionary result =...; IReadOnlyDictionary dict = result;, т.е. с таким же успехом описывается интерфейсом.

V>Работает этот трюк за счёт того, что время жизни объекта не обязательно равно времени жизни переменной.

V>Именно поэтому описание init-only сценариев настолько простое/элегантное — здравствуй выразительность! ))
Чем это выразительнее интерфейса-то?

v>>> Но даже в процессе мутации ключи таблицы были гарантированно иммутабельны.

V>·>Потому что копии.
V>Это может быть копия указателя, как в джаве.
V>Но тогда надо озаботиться распространением константности по указателям.
Я вроде выше уже демонстрировал как можно мутировать значение ключа на который ссылается таблица, сломав инвариант.
А с другой стороны — имея в яве HashMap<String, XXX> можно быть 100% уверенным, что ключ поменяться не может.

v>>> Без хаков если, то да.

V>·>И если из конструктора this не утекает, например.
V>Выше отписывался по этому кейзу.
Ты это назвал побочным эффектом, и это, как ты считаешь, даёт индульгенцию, т.к. в твою картину мира не укладывается. Отличный пример черри-пикинга фактов.

v>>> Данные/значения — это термин из механики происходящего, где by ref или by value имеют однозначную интерпретацию.

V>·>by value это скучный случай, но т.к. там копирование, то не всегда лучший.
V>Зависит от размеров данных.
V>Тут уже программист выбирает что эффективней — распространять ссылки и иметь косвенность на стороне вычислений, или распространять копии небольших объектов, где вычисления над ними происходят эффективней.
Угу. Я и говорю "не всегда".

V>·>Это тавтология какая-то. "Если гарантии есть, то гарантии есть". Вопрос и состоит в том — откуда собственно берутся эти гарантии отсутствия побочек и протечек? Ответ — обеспечиваются программистом. Вывод —

V>·>ты словоблудишь, т.к. речь идёт о гарантиях, даваемых компилятором.
V>Я уже упомянул вариант сделать все поля объекта const.
V>Т.е. возможность получить железобетонные гарантии тоже есть.
V>Тогда, даже если this протечёт, через этот this невозможно будет изменить init-only данные.
Что будет фактическим аналогом сделать все поля final.

V>Тут удобней рассуждать о "чистых функциях" (в том числе о конструкторах/деструкторах) и такие обсуждения ведутся.

V>Например, в некоторые языки добавляют ключевое слово clean, которое может дать обсуждаемые гарантии отсутствия протечек.
Угу. Я рад, что ты наконец-то согласился, что гарантий в плюсах для этого нет.

V>·>Более того, в реальном коде могут быть и чуть менее тривиальные куски кода, стоит чуток порефакторить и вместо const SomeObject * obj = new SomeObject(); сделать const SomeObject * obj = makeSomeObject(); — так компилятору становится вообще плевать на все твои const-старания. А в том же шарпе ImmutableList означает именно что иммутабельный список и ничего более, со 100% гарантией компилятора, без всяких допущений и недосказок.

V>Тогда обернуть объект в read-only view.
Как и в явах всяких. О чём я и говорил, много раз уже.

V>·>Угу. Верно. И в данном случае в java компилятор помогает больше. Наличие record тебе даёт гарантированно иммутабельный тип, и протечек никаких быть не может.

V>Угу, теперь построй на своих record реализацию иммутабельного словаря (в т.ч. хеш-таблицы) или приоритетной очереди.
Это уже совсем бред какой-то. Даже в эрлангах-хаскеллях такое не делают, ибо нафиг надо.

v>>> автоматом унаследовав всю инфраструктуру вокруг исходного типа, например, вычисление хеш-функции.

V>·>В java вместо этой всей колбасы в коде будет только @Builder record Index(int a, int b){} — создастся два типа — сам иммутабельный record и builder для него. Может не так красиво, зато билдер делается не в спеке ЯП, а либой.
V>Это всё понятно, если самому всё с 0-ля писать (и то, вопрос выше в силе).
V>Непонятно как использовать тысячи уже готовых типов в имммутабельных сценариях.
С осторожностью, как и в плюсах.

V>В плюсах это делается относительно дешево, а мы тут обсуждаем лишь цену, бо возможность описать на любом достаточно развитом ЯП иммутабельные сценарии я под сомнение не ставил.

Так и в шарпах-явах это делается относительно дёшево. Даже неясно относительно чего ты тут дешевизну намерил.

v>>> По константной ссылке/указателю ты можешь ссылаться на мутабельные и иммутабельные объекты, делая код ортогональным для данных.

V>·>Ага. Но не получается сделать ссылку на иммутабельный объект.
V>Абсолютно верно.
V>Эта ссылка может ссылаться как на мутабельный, так и на иммутабельный объект.
В этом и беда. Поэтому иммутабельными делают типы, а не объекты. И константность тут мало чем помогает.

V>Следовательно, гарантии происходят от того, кто эту ссылку передаёт во внешний мир — ведь только владелец данных "знает" характер своих данных.

V>А выполняемый read-only код единообразен (например, банальное логирование), отсюда ортогональность кода к данным.
Как и в явах-шарпах, разницы никакой.

v>>> Но в типе SomeDictionary const index резко становится иммутабельным.

V>·>Этот случай, кстати, поинтереснее. Т.к. это не голая структура, а есть куча всяких методов. И, внезапно, выясняется, что хоть и "резко", но уже не нахаляву — интерфейс SomeDictionary уже надо внимательно проектировать с разделением const/nonconst методов.
V>Ес-но.
V>Но это всё еще одно описание типа, где ты просто помечаешь read-only методы через const.
Именно. А ещё засада в том, что const-методы часто ещё и требуют копи-пастной реализации (ага-ага, бинарник увеличивается), в отличие от интерфейсов, которые требуют только сигнатуру.

V>У плюсовиков этот рефлекс на уровне мозжечка, бо является еще дополнительным документированием/аннотированием методов — профит! ))

V>"Читать" такие типы потом проще.
Субъективная вкусовщина.

v>>> — ссылки на константные данные не гарантируют константность тех данных, они накладывают ограничения — позволяют использовать лишь иммутабельное АПИ объектов.

V>·>константное, а не иммутабельное. Не жонглируй терминами.
V>Никакого жонглирования, бо здесь речь не об неизменяемых данных, а об неизменяемой обработке данных, т.е. не вносящих в данные изменения.
Твоя фраза должна была быть записана так: "ссылки на константные данные не гарантируют константность тех данных, они накладывают ограничения — позволяют использовать лишь константный АПИ объектов."
И внезапно она становится скучной тавтологией. А твоей фривольностью подменять константность и иммутабельность на ходу в случайных местах — ты игнорируешь важное отличие между этими двумя терминами.

V>Просто не надо складывать в кучу неизменяемые данные и алгоритмы, не изменяющие даные.

V>(просил уже)
А я просил не путать иммутабельность и константность.

v>>> Как именно данные случаются неизменяемыми — да как угодно.

v>>> Можно и безо-всяких const, record, final и прочих приблуд, достаточно того, чтобы в момент работы таких алгоритмов данные никто не изменял.
V>·>Гарантию "никто не изменял" компилятор может дать лишь для иммутабельных типов.
V>Это всё до некоторой степени, пока не пошли хаки, например, через реинтерпретацию памяти или рефлексию.
Или уж сразу — отвёрткой в работающией материнке поковыряться....

V>Лишь малое кол-во языков способно обеспечить доказуемость корректности своего кода.

V>И ведь не просто так эти языки нифига не мейнстримовые?
Ну вот хотя бы прогресс. Раньше ты утверждал, что есть гарантии. Ладно, что хоть согласился, что их на самом деле-то нет.

v>>> Тут мы рассуждаем лишь о том, как защититься от случайного изменения невнимательным программистом данных, которые мы хотим рассматривать как иммутабельные.

V>·>Так это константность называется, а не иммутабельность, опять термины поменяешь.
V>Разве?
V>

V>Неизменяемым (англ. immutable) называется объект, состояние которого не может быть изменено после создания.

Лосось — это рыба с хвостом.
V>

V>В языках программирования C, C++, C# и D const является квалификатором типа: ключевое слово применяется к типу данных, показывая, что данные константны (неизменяемы).

Сёмга — это рыба с хвостом.
Э. И? Из этого ты делаешь какой вывод? Что сёмга — это как селёдка?

Похоже это русская вики. Там под словом "неизменяемый" неясно что понимается в каком контексте. Разберись по нормальным докам, лучше англоязычным.

V>Если бы ключевое слово было не const, а immutable — тебе было бы легче? ))

Ты не поверишь, но в том же D — есть оба квалификатора! Вот изучи зачем их там два и чем они отличаются, доку найдёшь, надеюсь.

V>·>Если же прекратить черри-пикать факты, то надо рассуждать не лишь о том, что тебе по душе, но и об остальных аспектах, и ещё порассуждать о том не как "хотим рассматривать", а как иметь иммутабельность с гарантией компилятора.

V>Гарантии в любом случае недоказуемы в обсуждаемых языках.
В рекордах — данные гарантированно иммутабельны, по спеке языка. Но это не столь важно. Суть в том, что плюсы ничем не лучше по гарантиям других яп, хоть ты и обещал.

V>В джаве можно допустить ошибку — изменив данные через некий метод интерфейса, который (интерфейс) согласно прикладной семантики должен был давать read-only view.

В т.ч. и поэтому ввели рекорды.

V>В плюсах можно забыть приписать полю const и однажды наступить в это, и т.д. до бесконечности.

V>Более конструктивно будет обсуждать такой вариант, где программист выразил свои намерения верно и обеспечил "железобетонные гарантии".
V>(которые всё-равно обходятся через хаки и в джаве, и в плюсах, но мы об этом скромно умалчиваем, бгг).
Хаки можно запретить, по крайней мере в яве. (рефлексия отключается, модули запираются, етс).

V>В этих вариантах останется обсуждать лишь выразительность (т.е. трудоёмкость готового решения) и ничего более.

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

V>·>Не очень понял, что ты имеешь в виду перетянулы в шарп?

V>В шарпе появились константные ссылки — "readonly ref", или кратко "in" для параметров (применяется для value-типов и для переменных-ссылок на GC-типы), а так же полные аналоги const-методов из плюсов — это "readonly" методы у value-типов.
V>Всё вместе служит той же цели — выразительности кода при достижении тех или иных гарантий.
Ясно. Надо почитать... Вполне верю, что идею константности можно довести до ума... но в том виде в каком она есть в плюсах — по-моему так себе...

v>>> Еще как подмножество.

V>·>Я привёл явный пример когда это не так.
V>Не видел.
V>И оно не может быть "не так" согласно определению иммутабельности и семантики кода, получающего read-only доступ к данным.
Если ты напишешь код, обеспечивающий read-only, и код обеспечивающий иммутабельность — это будет разный код.

v>>> Наоборот, упрощение, бо комбинаторный подход позволяет упростить базу — меньше писать.

V>·>Даже по количеству буковок не сильно отличается.
V>Не скажи.))
V>Я привёл примёр read-only view на шарпе (на джаве можно аналогично, разве что +1 к косвенности), а в плюсах достаточно было простого ключевого слова const безо всей этой возни.
Это ты яблоки с апельсинами сравнил. В том же шарпе уже есть ReadOnlyDictionary, тоже никакой возни.

V>·>Я уже об этом упоминал. Нет никакого утяжеления. Интерфейсы в яве практически бесплатны.

V>Вранье. ))
V>Интерфейсы в джаве намного тяжелее прямых методов.
V>Бесплатны они только если компилятор или JIT "видят" жизненный цикл ссылки — тогда может заменить вызов метода через интерфейс прямым вызовом.
Нет. Там ставится uncommon trap. А то и совсем, если у интерфейса ровно одна загруженная реализация — оно его девиртуализует и инлайнит.

V>·>Не экономит она труд.

V>Выше пример, который не нужен в плюсах.
В шарпах и этого не нужно писать. Труда ещё меньше.

V>·>Насчёт размера тут тоже сложно всё. Неясно как можно адекватно сравнивать бинари плюсов и явы... слишком много нюансов.

V>Надо сравнивать бинари в рамках одной техонлогии.
V>Например, можно сравнивать бинари в плюсах при применении подхода джавы vs родного плюсового подхода.
V>Или можно сравнивать бинари в шарпе, где доступны оба подхода для value-типов.
В зависимости от способа сравнения можно получить противоположные результаты.

V>В любом случае, лишний код есть лишний код.

Так отличие может быть лишь в наличии интерфейса. Интерфейс — это просто список методов. И всё.

V>·>Если я правильно помню, то это аналог final-поля, который был в java с рождения.

V>Это изначально применительно к полям объектов.
V>Затем readonly стало можно применять к аргументам методов и возвращаемым значениям.
V>Затем через readonly стало можно помечать методы, где этот метод не может изменять внутренние поля — компилятор не даст.
Ясно, возможно. Может когда-то и плюсы до ума доведут.

v>>> Вплоть до того, что даже в иммутабельных сценариях в джаве редко кто заморачивается с внутренней гарантией этой иммутабельности.

V>·>Не понимаю что ты понимаешь под сложностью.
V>Лишний код.
V>Тот же твой Defensive copying как малая часть этой сложности.
V>Например, в дотнете достаточно передавать DateTime по значению или по константной ссылке, а в джаве надо делать GC-копию объекта на каждый чих.
Уже давным давно в джаве сделали иммутабельное дату-время, а до этого были сторонние либы. Этой проблемы больше нет.

V>Да там уже просто рука устаёт всё это внимательно описывать. ))

Это вопрос конкретного дизайна конкретного АПИ. Да, можно накрутить сложности на пустом месте. Старый дизайн datetime api не пнул только ленивый, там было дохрена проблем, первый блин комом. Какой-то присущей сложности нет.
Re[91]: Когда это наконец станет defined behavior?
Здравствуйте, vdimas, Вы писали:

v>>> ·>покажи пример кода как обеспечить иммутабельность через view мутабельного объекта.

v>>> Легко — достаточно обеспечить отсутствие протекание ссылки на мутабельный объект.
v>>> (например, создавать приватный мутабельный объект в конструкторе view)
V>·>Не очень понял что ты имеешь в виду, продемонстрируй кодом.
V>Берёшь достаточно сложный мутабельный объект и прикручиваешь сверху иммутабельный view.
Ясно. Просто ты опять плаваешь в терминологии и делаешь странные обобщения. Тут у тебя не "иммутабельность может быть св-вом объекта, а может быть адаптером-view", а "иммутабельность может быть обёрткой мутабельного типа", тупо инкапсуляция. В этом принципиальное отличие от константности, ведь константность действительно можно обеспечить "через view мутабельного объекта" — т.е. во view оборачивается инстанс типа (т.е. объект).

И вот тут я опять подозреваю, что ты нарочно флейм разводишь и цирк устраиваешь. Я попросил продемонстрировать "пример кода как обеспечить иммутабельность", а ты мне вываливаешь ReadOnlyDictionary, как нарочно мешаешь всё в кучу: кручу, верчу, запутать хочу.
Или ты вообще невдупляешь что такое иммутабельность. Если так, то почитай доку .NET чем отличаются IImmutableDictionary и IReadOnlyDictionary.

v>>> Достаточно просто — объявляешь данные как const и они автоматом становятся иммутабельными.

V>·>А дальше что? В лучшем случае ты их можешь такими сделать в пределах локальной переменной или поля класса.
V>Для глобальных переменных аналогично.
А, ну да, забыл про это. Ибо глобальные переменные — зло.

V>·>В другом скопе он уже теряет иммутабельность.

V>Если речь про эти же самые данные — то иммутабельность не теряется.
Теряется с т.з. компилятора. Иммутабельность данных по конст-ссылкам никак не гарантируется компилятором.

V>Но можно получить мутабельную копию данных (в общем случае, если не запрещать конструктор копирования), где изменения в копии не будут влиять на исходные данные.

Дело не в этом. Дело в том, что все те ништяки, которые имеются у иммутабельных типов — исчезают, т.к. имея даже "const X" нельзя быть уверенным — иммутабельный он или нет. И совсем плохо, если у тебя "const X*" — тут вообще беда.

V>·>По значению это и есть defensive copy.

V>Э, нет. ))
V>Это в джаве такая техника, связанная с тем, что все объекты имеют ссылочную семантику.
Нет. Вот это твоё "data.ToDictionary(item => item.key, item => item.value)" — и есть defensive copy — ты копируешь данные внтурь объекта и не даёшь к ним мутирующий доступ, инкапсуляция обычная. А твоё "Wrap(Dictionary<TKey, TValue> dict) => new(dict)" — это бага. Оно тоже копию должно создавать, иначе это вовсе не иммутабельный тип как ты обещал выше, а ReadOnly view.

V>Тогда, если объект не предоставляет явного read-only АПИ, необходимо создавать его копию при подаче такого объекта в кач-ве аргументов, чтобы вызываемый код не изменил состояние вызывающего кода.

Только иммутабельность для этого не нужна, для этого достаточно константности. И даже копию делать вовсе не обязательно — можно просто RO-view обёртку для того же объекта.

V>В этом месте я и стебался, что в джаве не всегда даже заморачиваются с такими вещами — банально можно прохлопать или полениться, типа, вызывающий код достоверно не изменяет аргументы... а потом оп-па при очередных изменениях — получи фашист гранату! ))

Не заморачиваются, как правило, для простоты, из-за лени...

V>В дотнете такие вещи обыгрываются автоматически через value-type, где при передаче по-значению копия создаётся автоматом.

В дотнете создают ImmutableList, ReadOnlyCollection, string/StringBuilder и т.п. Иначе получается фигня, как у тебя в плюсах.

V>Второй способ — это передача readonly-ссылки на value-type значение.

Опять же read-only. Мы говорим об иммутабельности же.


v>>> ·>Ответ — да никак.

v>>> Для этого необходимо показать хотя бы минимальный пример этого "никак".
V>·>Пример чего? Пример кода, который никак не написать??!
V>Пример нарушения иммутабельности константного объекта.
Через утечку this в конструкторе, например, ты же сам этот код где-то выше показывал. Или вообще — запустили в конструкторе новый тред и меняем там this когда захочется. А компилятору — пофиг.

v>>> Это лишь один из трюков, достижимых при иммутабельности.

v>>> Так же как шаренье иммутабельных данных без блокировки м/у потоками.
V>·>Угу. А константные объекты даже пошарить нельзя.
V>Константные объекты не можно, а нужно шарить. ))
V>Собсно, зачастую для таких сценариев константные объекты и используются, включая обычные константы.
Но там, где они пошарены — уже нельзя рассчитывать на плюшки иммутабельости.

V>·>Да, но там создаётся иммутабельная копия из данных мутабельного билдера. Как и в шарпах всяческих.

V>Есть еще несколько вариантов:
V>* Данные не пересоздаются в конце работы builder, а меняют своего владельца, сам builder помечает этот факт в данных и, если построение данных продолжится после выдачи иммутабельного объекта, то builder создаст себе новую копию данных для дальнейшего над ними измывательства;
V>* Данные в конце работы builder меняют своего владельца, при этом сам builder обнуляется. Дальнейшее построение либо невозможно, либо ведётся с нуля.
Это уже мелкие оптимизации. Со своими преимуществами. И недостатками (которые ты скромно умалчиваешь).

v>>> Вот после создания иммутабельного объекта, все эти трюки можно использовать.

V>·>С иммутабельным можно. С константным — не всегда, только компилятор гарантии не даёт.
V>Я попросил пример, где компилятор не даёт гарантии.
Я показал выше.

v>>> ·>Как и в Плюсах.

v>>> В плюсах компилятор больно бъёт тебе по пальцам.
V>·>Компилятор ничего про иммутабельность не знает, поэтому по пальцам бить тупо не может. Нет такого понятия в стандарте.
V>Другой термин, бо в стандарте описаны конструкции языка, а не прикладная роль данных.
Ну в яве почему-то не постеснялись написать "другой термин" (нет, не просто термин, а другая семантическая конструкция) в описании рекордов.

V>·>ИЧСХ, ты это и так прекрасно понимаешь (когда, например, ты скромно "забыл" назвать объект типа const SomeObj иммутабельным). Я не понимаю зачем ты тут этот цирк устраиваешь?

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

v>>> ·>Ровно твои слова же: "Если протечек ссылок для мутабельности нет, то автоматом получается иммутабельность". К чему сарказм-то?

v>>> К тому, что компилятор в плюсах отслеживает попытки "протечек" и отказывается компилировать такой код.
V>·>Не отслеживает. Сам же ниже пишешь, например, о сценарии утечки this из конструктора.
V>Это классический побочный эффект.
V>Тоже иногда сильная и нужная весчь, просто не всегда сочетается с иммутабельными сценариями.
Угу. Т.е. не отслеживает. ЧТД.

V>Чтобы сочетать побочные эффекты с иммутабельностью, данные надо сразу описывать как иммутабельные (init-only), т.е. все поля объекта должны быть const и инициализироваться до тела конструктора (в списке инициализации переменных), т.е. до возможности оперировать переменной this.

Или как final-поля в яве.

V>Тогда строгая иммутабельность объекта будет обеспечена даже без ключевого слова const перед переменной его типа. ))

Именно. const для иммутабельности не нужен.

V>Но это крайний случай, который не сильно интересен, потому что не получаем плюшек выразительности/обобщения.

V>Такая возможность "просто есть", но используется редко, бо редко надо совмещать иммутабельность с побочными эффектами.
V>Но зато когда надо — понятно что делать.


v>>> ·>Вопрос в том, как собственно обеспечить отсутствие протечек.

v>>> result[{42, 43}] = "42, 43";
V>·>Здесь формально копиктор struct срабатывает.
V>Здесь сам словарь мутабельный, ключ словаря иммутабельный, данные по ключу перезаписываемы.
V>result[index] возвращает non-const ссылку на значение, по этой ссылке происходит присвоение нового значения.
Тут срабатыват копиктор структуры, как примерно вот так index key{42, 43}; result[key] = "42, 43";. Внутри мапы создаётся копия ключа.

V>·>И здесь формально копия.

V>Формально копия, верно.
V>А на деле никакой копии — дополнительные временные объекты не создаются.
На деле это необязательная оптимизация.

V>·>Но допустим у тебя есть этот самый dict офигенного размера и он тут на стеке вроде как иммутабельный. Как его теперь обработать из другого треда так, чтобы этот самый другой тред был уверен, что ему передают только иммутабельные объекты?

V>Это зависит от взаимного времени жизни — поток не должен пережить время жизни такой переменной (например, создали такую переменную в main и передали программе дальше).
Ну дестркуторы отдельная тема. Деструктор, к сожалению, может поменять состояние даже супер-пупер константно-иммутабельного объекта.

V>Или же такая переменная может быть глобальной/статической, как я и показал в примере.

Проблема в том, что этот же тред потом можно будет внезапно вызвать с мутабельным объектом.

v>>> В данном примере гарантируется, что переменная dict иммутабельная, хотя на этапе построения мы пошагово мутировали значение.

V>·>Это немного враньё. Значение мы мутировали у result, а dict это копия.
V>Формально да.
V>А на деле происходили операции в одной области памяти, т.е. получился классический кейз иммутабельных данных — init-only.
Это оптимизации всё. После jit тоже много чего интересного происходит.

V>В этом примере через формальное якобы создание копии разделяется время жизни мутабельной переменной result и иммутабельной переменной dict, где вторая становится доступной для оперирования строго после того, как переменная result стала недоступной. Хотя, обе переменные ссылаются на одну область памяти.

Это аналогично Dictionary result =...; IReadOnlyDictionary dict = result;, т.е. с таким же успехом описывается интерфейсом.

V>Работает этот трюк за счёт того, что время жизни объекта не обязательно равно времени жизни переменной.

V>Именно поэтому описание init-only сценариев настолько простое/элегантное — здравствуй выразительность! ))
Чем это выразительнее интерфейса-то?

v>>> Но даже в процессе мутации ключи таблицы были гарантированно иммутабельны.

V>·>Потому что копии.
V>Это может быть копия указателя, как в джаве.
V>Но тогда надо озаботиться распространением константности по указателям.
Я вроде выше уже демонстрировал как можно мутировать значение ключа на который ссылается таблица, сломав инвариант.
А с другой стороны — имея в яве HashMap<String, XXX> можно быть 100% уверенным, что ключ поменяться не может.

v>>> Без хаков если, то да.

V>·>И если из конструктора this не утекает, например.
V>Выше отписывался по этому кейзу.
Ты это назвал побочным эффектом, и это, как ты считаешь, даёт индульгенцию, т.к. в твою картину мира не укладывается. Отличный пример черри-пикинга фактов.

v>>> Данные/значения — это термин из механики происходящего, где by ref или by value имеют однозначную интерпретацию.

V>·>by value это скучный случай, но т.к. там копирование, то не всегда лучший.
V>Зависит от размеров данных.
V>Тут уже программист выбирает что эффективней — распространять ссылки и иметь косвенность на стороне вычислений, или распространять копии небольших объектов, где вычисления над ними происходят эффективней.
Угу. Я и говорю "не всегда".

V>·>Это тавтология какая-то. "Если гарантии есть, то гарантии есть". Вопрос и состоит в том — откуда собственно берутся эти гарантии отсутствия побочек и протечек? Ответ — обеспечиваются программистом. Вывод —

V>·>ты словоблудишь, т.к. речь идёт о гарантиях, даваемых компилятором.
V>Я уже упомянул вариант сделать все поля объекта const.
V>Т.е. возможность получить железобетонные гарантии тоже есть.
V>Тогда, даже если this протечёт, через этот this невозможно будет изменить init-only данные.
Что будет фактическим аналогом сделать все поля final.

V>Тут удобней рассуждать о "чистых функциях" (в том числе о конструкторах/деструкторах) и такие обсуждения ведутся.

V>Например, в некоторые языки добавляют ключевое слово clean, которое может дать обсуждаемые гарантии отсутствия протечек.
Угу. Я рад, что ты наконец-то согласился, что гарантий в плюсах для этого нет.

V>·>Более того, в реальном коде могут быть и чуть менее тривиальные куски кода, стоит чуток порефакторить и вместо const SomeObject * obj = new SomeObject(); сделать const SomeObject * obj = makeSomeObject(); — так компилятору становится вообще плевать на все твои const-старания. А в том же шарпе ImmutableList означает именно что иммутабельный список и ничего более, со 100% гарантией компилятора, без всяких допущений и недосказок.

V>Тогда обернуть объект в read-only view.
Как и в явах всяких. О чём я и говорил, много раз уже.

V>·>Угу. Верно. И в данном случае в java компилятор помогает больше. Наличие record тебе даёт гарантированно иммутабельный тип, и протечек никаких быть не может.

V>Угу, теперь построй на своих record реализацию иммутабельного словаря (в т.ч. хеш-таблицы) или приоритетной очереди.
Это уже совсем бред какой-то. Даже в эрлангах-хаскеллях такое не делают, ибо нафиг надо.

v>>> автоматом унаследовав всю инфраструктуру вокруг исходного типа, например, вычисление хеш-функции.

V>·>В java вместо этой всей колбасы в коде будет только @Builder record Index(int a, int b){} — создастся два типа — сам иммутабельный record и builder для него. Может не так красиво, зато билдер делается не в спеке ЯП, а либой.
V>Это всё понятно, если самому всё с 0-ля писать (и то, вопрос выше в силе).
V>Непонятно как использовать тысячи уже готовых типов в имммутабельных сценариях.
С осторожностью, как и в плюсах.

V>В плюсах это делается относительно дешево, а мы тут обсуждаем лишь цену, бо возможность описать на любом достаточно развитом ЯП иммутабельные сценарии я под сомнение не ставил.

Так и в шарпах-явах это делается относительно дёшево. Даже неясно относительно чего ты тут дешевизну намерил.

v>>> По константной ссылке/указателю ты можешь ссылаться на мутабельные и иммутабельные объекты, делая код ортогональным для данных.

V>·>Ага. Но не получается сделать ссылку на иммутабельный объект.
V>Абсолютно верно.
V>Эта ссылка может ссылаться как на мутабельный, так и на иммутабельный объект.
В этом и беда. Поэтому иммутабельными делают типы, а не объекты. И константность тут мало чем помогает.

V>Следовательно, гарантии происходят от того, кто эту ссылку передаёт во внешний мир — ведь только владелец данных "знает" характер своих данных.

V>А выполняемый read-only код единообразен (например, банальное логирование), отсюда ортогональность кода к данным.
Как и в явах-шарпах, разницы никакой.

v>>> Но в типе SomeDictionary const index резко становится иммутабельным.

V>·>Этот случай, кстати, поинтереснее. Т.к. это не голая структура, а есть куча всяких методов. И, внезапно, выясняется, что хоть и "резко", но уже не нахаляву — интерфейс SomeDictionary уже надо внимательно проектировать с разделением const/nonconst методов.
V>Ес-но.
V>Но это всё еще одно описание типа, где ты просто помечаешь read-only методы через const.
Именно. А ещё засада в том, что const-методы часто ещё и требуют копи-пастной реализации (ага-ага, бинарник увеличивается), в отличие от интерфейсов, которые требуют только сигнатуру.

V>У плюсовиков этот рефлекс на уровне мозжечка, бо является еще дополнительным документированием/аннотированием методов — профит! ))

V>"Читать" такие типы потом проще.
Субъективная вкусовщина.

v>>> — ссылки на константные данные не гарантируют константность тех данных, они накладывают ограничения — позволяют использовать лишь иммутабельное АПИ объектов.

V>·>константное, а не иммутабельное. Не жонглируй терминами.
V>Никакого жонглирования, бо здесь речь не об неизменяемых данных, а об неизменяемой обработке данных, т.е. не вносящих в данные изменения.
Твоя фраза должна была быть записана так: "ссылки на константные данные не гарантируют константность тех данных, они накладывают ограничения — позволяют использовать лишь константный АПИ объектов."
И внезапно она становится скучной тавтологией. А твоей фривольностью подменять константность и иммутабельность на ходу в случайных местах — ты игнорируешь важное отличие между этими двумя терминами.

V>Просто не надо складывать в кучу неизменяемые данные и алгоритмы, не изменяющие даные.

V>(просил уже)
А я просил не путать иммутабельность и константность.

v>>> Как именно данные случаются неизменяемыми — да как угодно.

v>>> Можно и безо-всяких const, record, final и прочих приблуд, достаточно того, чтобы в момент работы таких алгоритмов данные никто не изменял.
V>·>Гарантию "никто не изменял" компилятор может дать лишь для иммутабельных типов.
V>Это всё до некоторой степени, пока не пошли хаки, например, через реинтерпретацию памяти или рефлексию.
Или уж сразу — отвёрткой в работающией материнке поковыряться....

V>Лишь малое кол-во языков способно обеспечить доказуемость корректности своего кода.

V>И ведь не просто так эти языки нифига не мейнстримовые?
Ну вот хотя бы прогресс. Раньше ты утверждал, что есть гарантии. Ладно, что хоть согласился, что их на самом деле-то нет.

v>>> Тут мы рассуждаем лишь о том, как защититься от случайного изменения невнимательным программистом данных, которые мы хотим рассматривать как иммутабельные.

V>·>Так это константность называется, а не иммутабельность, опять термины поменяешь.
V>Разве?
V>

V>Неизменяемым (англ. immutable) называется объект, состояние которого не может быть изменено после создания.

селёдка — это рыба с хвостом.
V>

V>В языках программирования C, C++, C# и D const является квалификатором типа: ключевое слово применяется к типу данных, показывая, что данные константны (неизменяемы).

Сёмга — это рыба с хвостом.
Э. И? Из этого ты делаешь какой вывод? Что сёмга — это как селёдка?

Похоже это русская вики. Там под словом "неизменяемый" неясно что понимается в каком контексте. Разберись по нормальным докам, лучше англоязычным.

V>Если бы ключевое слово было не const, а immutable — тебе было бы легче? ))

Ты не поверишь, но в том же D — есть оба квалификатора! Вот изучи зачем их там два и чем они отличаются, доку найдёшь, надеюсь.

V>·>Если же прекратить черри-пикать факты, то надо рассуждать не лишь о том, что тебе по душе, но и об остальных аспектах, и ещё порассуждать о том не как "хотим рассматривать", а как иметь иммутабельность с гарантией компилятора.

V>Гарантии в любом случае недоказуемы в обсуждаемых языках.
В рекордах — данные гарантированно иммутабельны, по спеке языка. Но это не столь важно. Суть в том, что плюсы ничем не лучше по гарантиям других яп, хоть ты и обещал.

V>В джаве можно допустить ошибку — изменив данные через некий метод интерфейса, который (интерфейс) согласно прикладной семантики должен был давать read-only view.

В т.ч. и поэтому ввели рекорды.

V>В плюсах можно забыть приписать полю const и однажды наступить в это, и т.д. до бесконечности.

V>Более конструктивно будет обсуждать такой вариант, где программист выразил свои намерения верно и обеспечил "железобетонные гарантии".
V>(которые всё-равно обходятся через хаки и в джаве, и в плюсах, но мы об этом скромно умалчиваем, бгг).
Хаки можно запретить, по крайней мере в яве. (рефлексия отключается, модули запираются, етс).

V>В этих вариантах останется обсуждать лишь выразительность (т.е. трудоёмкость готового решения) и ничего более.

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

V>·>Не очень понял, что ты имеешь в виду перетянулы в шарп?

V>В шарпе появились константные ссылки — "readonly ref", или кратко "in" для параметров (применяется для value-типов и для переменных-ссылок на GC-типы), а так же полные аналоги const-методов из плюсов — это "readonly" методы у value-типов.
V>Всё вместе служит той же цели — выразительности кода при достижении тех или иных гарантий.
Ясно. Надо почитать... Вполне верю, что идею константности можно довести до ума... но в том виде в каком она есть в плюсах — по-моему так себе...

v>>> Еще как подмножество.

V>·>Я привёл явный пример когда это не так.
V>Не видел.
V>И оно не может быть "не так" согласно определению иммутабельности и семантики кода, получающего read-only доступ к данным.
Если ты напишешь код, обеспечивающий read-only, и код обеспечивающий иммутабельность — это будет разный код.

v>>> Наоборот, упрощение, бо комбинаторный подход позволяет упростить базу — меньше писать.

V>·>Даже по количеству буковок не сильно отличается.
V>Не скажи.))
V>Я привёл примёр read-only view на шарпе (на джаве можно аналогично, разве что +1 к косвенности), а в плюсах достаточно было простого ключевого слова const безо всей этой возни.
Это ты яблоки с апельсинами сравнил. В том же шарпе уже есть ReadOnlyDictionary, тоже никакой возни.

V>·>Я уже об этом упоминал. Нет никакого утяжеления. Интерфейсы в яве практически бесплатны.

V>Вранье. ))
V>Интерфейсы в джаве намного тяжелее прямых методов.
V>Бесплатны они только если компилятор или JIT "видят" жизненный цикл ссылки — тогда может заменить вызов метода через интерфейс прямым вызовом.
Нет. Там ставится uncommon trap. А то и совсем, если у интерфейса ровно одна загруженная реализация — оно его девиртуализует и инлайнит.

V>·>Не экономит она труд.

V>Выше пример, который не нужен в плюсах.
В шарпах и этого не нужно писать. Труда ещё меньше.

V>·>Насчёт размера тут тоже сложно всё. Неясно как можно адекватно сравнивать бинари плюсов и явы... слишком много нюансов.

V>Надо сравнивать бинари в рамках одной техонлогии.
V>Например, можно сравнивать бинари в плюсах при применении подхода джавы vs родного плюсового подхода.
V>Или можно сравнивать бинари в шарпе, где доступны оба подхода для value-типов.
В зависимости от способа сравнения можно получить противоположные результаты.

V>В любом случае, лишний код есть лишний код.

Так отличие может быть лишь в наличии интерфейса. Интерфейс — это просто список методов. И всё.

V>·>Если я правильно помню, то это аналог final-поля, который был в java с рождения.

V>Это изначально применительно к полям объектов.
V>Затем readonly стало можно применять к аргументам методов и возвращаемым значениям.
V>Затем через readonly стало можно помечать методы, где этот метод не может изменять внутренние поля — компилятор не даст.
Ясно, возможно. Может когда-то и плюсы до ума доведут.

v>>> Вплоть до того, что даже в иммутабельных сценариях в джаве редко кто заморачивается с внутренней гарантией этой иммутабельности.

V>·>Не понимаю что ты понимаешь под сложностью.
V>Лишний код.
V>Тот же твой Defensive copying как малая часть этой сложности.
V>Например, в дотнете достаточно передавать DateTime по значению или по константной ссылке, а в джаве надо делать GC-копию объекта на каждый чих.
Уже давным давно в джаве сделали иммутабельное дату-время, а до этого были сторонние либы. Этой проблемы больше нет.

V>Да там уже просто рука устаёт всё это внимательно описывать. ))

Это вопрос конкретного дизайна конкретного АПИ. Да, можно накрутить сложности на пустом месте. Старый дизайн datetime api не пнул только ленивый, там было дохрена проблем, первый блин комом. Какой-то присущей сложности нет.