Re[106]: Когда это наконец станет defined behavior?
От: vdimas Россия  
Дата: 02.09.23 22:37
Оценка:
Здравствуйте, vopl, Вы писали:

V>- объект будет один а не два (со всеми вытекающими)


С какими еще вытеающими?
Пример можно?
Re[89]: Когда это наконец станет defined behavior?
От: · Великобритания  
Дата: 02.09.23 22:59
Оценка:
Здравствуйте, vdimas, Вы писали:
v> ·>А ты заканчивай коньяк по утрам пить
v> Без обид, но упор на жонглирование терминами
Я терминами не жонглировал.

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

v> Легко — достаточно обеспечить отсутствие протекание ссылки на мутабельный объект.
v> (например, создавать приватный мутабельный объект в конструкторе view)
Не очень понял что ты имеешь в виду, продемонстрируй кодом.

v> ·>Вопрос в том, как же компилятор проверяет/помогает гарантировать отстуствие протечек с помощью const?

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

v> Иммутабельность автоматически распространяется при владении другими объектами по-значению.

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

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

v> Для этого необходимо показать хотя бы минимальный пример этого "никак".
Пример чего? Пример кода, который никак не написать??!

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

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

v> Но иммутабельные данные необходимо как-то создавать.

v> И наиболее эффективно их создавать в мутабельной манере.
v> Я уже отсылал к паттерну mutable_builder => immutable_object.
Да, но там создаётся иммутабельная копия из данных мутабельного билдера. Как и в шарпах всяческих.

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

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

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

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

v> Собсно, новички в плюсах зачастую маются с компиллированием программ именно из-за попыток нарушения константности.

Это, кстати, тоже минус. С интерфейсами же всё проще — есть метод, можно дёрнуть, нет метода — нельзя дёрнуть.

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

v> К тому, что компилятор в плюсах отслеживает попытки "протечек" и отказывается компилировать такой код.
Не отслеживает. Сам же ниже пишешь, например, о сценарии утечки this из конструктора.

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

v> result[{42, 43}] = "42, 43";
Здесь формально копиктор struct срабатывает.

v> // но здесь иммутабельный объект

v> const SomeDictionary dict = buildDictionary();
И здесь формально копия.

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

v> ·>И где что там у тебя автоматически гарантируется-то с помощью const?

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

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

Потому что копии.

v> ·>Допустим, у тебя есть где-то const SomeThing someValue(42); — ты гарантируешь, что someValue — иммутабельно?

v> Без хаков если, то да.
И если из конструктора this не утекает, например.

v> v>> И для обычных даных это в точности равно иммутабельности.

v> ·>Повторюсь. Данные — не объекты (если я правильно понял, что ты подразумеваешь под данными). Мы говорим об объектах.
v> Ты опять разбрасываешься терминами.
v> Объект — это не только термин из ООП-парадигмы. В плюсах объектами является всё, даже простой int.
v> Данные/значения — это термин из механики происходящего, где by ref или by value имеют однозначную интерпретацию.
by value это скучный случай, но т.к. там копирование, то не всегда лучший.

v> ·>Приобретаемым как? Только заботой программиста. Компайлер c const ничего гарантировать не может.


v> Здесь оператор new возвращает указатель на неконстантный объект, но мы его сохраняем в указатель на константный объект.

v> Если конструктор SomeObject не имеет побочных эффектов (не сохраняет указатель на себя где-нить в глобальной переменной, т.е. если нет протечек), то гарантии получаются железобетонные.
Это тавтология какая-то. "Если гарантии есть, то гарантии есть". Вопрос и состоит в том — откуда собственно берутся эти гарантии отсутствия побочек и протечек? Ответ — обеспечиваются программистом. Вывод —
ты словоблудишь, т.к. речь идёт о гарантиях, даваемых компилятором.

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

v> ·>В java record — гарантии есть, кстати.

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

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

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

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

Ага. Но не получается сделать ссылку на иммутабельный объект.

v> ·>Это ложится на программиста, что в плюсах, что в джаве, что в шарпах. const для иммутабельности не нужен. Нужен record.

v> Это вам в джаве нужен, т.к. иммутабельность обеспечивается прикладной семантикой, где record — лишь синтаксический сахар для такой семантики.
Ну не совсем. В java обычно не делают что-то ради одной цели. records ввели в яп ещё и для реализации pattern matching.

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

Этот случай, кстати, поинтереснее. Т.к. это не голая структура, а есть куча всяких методов. И, внезапно, выясняется, что хоть и "резко", но уже не нахаляву — интерфейс SomeDictionary уже надо внимательно проектировать с разделением const/nonconst методов.

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

константное, а не иммутабельное. Не жонглируй терминами.

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

v> Можно и безо-всяких const, record, final и прочих приблуд, достаточно того, чтобы в момент работы таких алгоритмов данные никто не изменял.
Гарантию "никто не изменял" компилятор может дать лишь для иммутабельных типов.

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

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

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

v> В общем, подход плюсов оказался настолько удобным, что его перетянули и в C# (но только для value-типов) и даже в последние версии FreePascal.

v> Ранее в этих языках был доступен только подход джавы.
Не очень понял, что ты имеешь в виду перетянулы в шарп?

v> ·>Нет, не подмножеством. read-only view для мутабельного объекта — не подмножество иммутабельного сценария, ну никак.

v> Еще как подмножество.
Я привёл явный пример когда это не так.

v> v>> И на выходе у нас комбинаторика сценариев, а не сумма их.

v> ·>Осталось понять ценность этого комбинаторного усложнения на ровном месте
v> Наоборот, упрощение, бо комбинаторный подход позволяет упростить базу — меньше писать.
Даже по количеству буковок не сильно отличается.

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

v> В джаве тебе пришлось бы делать два типа дерева и два кода для их обхода (или выкручиваться через интерфйесы, что утяжеляет/тормозит).
Я уже об этом упоминал. Нет никакого утяжеления. Интерфейсы в яве практически бесплатны.

v> ·>константность — не является необходимостью

v> Разумеется, константность не является необходимостью для иммутабельных сценариев.
v> Она лишь дико экономит труд програмиста и размер конечного бинаря, а так-то фигня полная.
Не экономит она труд. Насчёт размера тут тоже сложно всё. Неясно как можно адекватно сравнивать бинари плюсов и явы... слишком много нюансов.

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

v> Наоборот, в шарпе вот относительно недавно добавили.
v> А потом опять добавили.
v> Ключевое слово чуть другое — readonly, но смысл и сценарии использования те же.
Если я правильно помню, то это аналог final-поля, который был в java с рождения.

v> ·>Тогда нафиг никакой комбинаторики и всё просто.

v> Сложнее, обычно.
v> Причём, гораздо.
v> Вплоть до того, что даже в иммутабельных сценариях в джаве редко кто заморачивается с внутренней гарантией этой иммутабельности.
Не понимаю что ты понимаешь под сложностью.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[115]: Когда это наконец станет defined behavior?
От: vopl Россия  
Дата: 03.09.23 16:46
Оценка:
Здравствуйте, vdimas, Вы писали:

Я уже сдался, ты уже победил.
Автор: vopl
Дата: 22.08.23
Нет смысла дополнительно дискутировать.
Re[90]: Когда это наконец станет defined behavior?
От: vdimas Россия  
Дата: 04.09.23 12:53
Оценка:
Здравствуйте, ·, Вы писали:

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

v>> Легко — достаточно обеспечить отсутствие протекание ссылки на мутабельный объект.
v>> (например, создавать приватный мутабельный объект в конструкторе view)
·>Не очень понял что ты имеешь в виду, продемонстрируй кодом.

Берёшь достаточно сложный мутабельный объект и прикручиваешь сверху иммутабельный view.
Один из популярных сценариев — какой-нить глобальный справочник.
На шарпе пара способов:
////////////////////////////////
public struct ReadOnlyDictionary<TKey, TValue> where TKey : notnull 
{
    private Dictionary<TKey, TValue> _dict;
  
    public int Count => _dict.Count;

    public TValue this[TKey key] => _dict[key];

    // наполняем приватный словарь в конструкторе
    public ReadOnlyDictionary((TKey key, TValue value)[] data)
        => _dict = data.ToDictionary(item => item.key, item => item.value);

    // обёртка над существующим словарём -
    //   потенциально небезопасная конструкция, поэтому приватная
    private ReadOnlyDictionary(Dictionary<TKey, TValue> dict) 
        =>_dict = dict;

    // пусть программист выражает намерения явно
    public static ReadOnlyDictionary<TKey, TValue> Wrap(Dictionary<TKey, TValue> dict) 
        => new(dict);
}

////////////////////////////////
public static class SomeSubsystem 
{
    private static (int key, string value)[] GetData() => new (int, string)[] {
        (42, "42"), 
        (43, "43")
    };

    // храним и возвращаем иммутабельный view
    public static ReadOnlyDictionary<int, string> Dict1 { get; } = new(GetData());

    // храним приватный мутабельный словарь
    private static Dictionary<int, string> dict2 = GetData().ToDictionary(item => item.key, item => item.value);

    // возвращаем публичную иммутабельную обертку над приватным мутабельным объектом
    public static ReadOnlyDictionary<int, string> Dict2 => ReadOnlyDictionary<int, string>.Wrap(dict2);
}

////////////////////////////////
class Program
{
    static void Main() {
         Console.WriteLine(SomeSubsystem.Dict1[42]);
         Console.WriteLine(SomeSubsystem.Dict2[43]);
    }
}



v>> ·>Вопрос в том, как же компилятор проверяет/помогает гарантировать отстуствие протечек с помощью const?

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

Для глобальных переменных аналогично.


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


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


v>> Иммутабельность автоматически распространяется при владении другими объектами по-значению.

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

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

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

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

Первый способ является defensive copy, второй способ заимствован у плюсов (причём, относительно недавно).

Ну и, для GC-объектов в дотнете ситуация ровно как в джаве и решается точно так же.


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

v>> Для этого необходимо показать хотя бы минимальный пример этого "никак".
·>Пример чего? Пример кода, который никак не написать??!

Пример нарушения иммутабельности константного объекта.


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

v>> Это лишь один из трюков, достижимых при иммутабельности.
v>> Так же как шаренье иммутабельных данных без блокировки м/у потоками.
·>Угу. А константные объекты даже пошарить нельзя.

Константные объекты не можно, а нужно шарить. ))
Собсно, зачастую для таких сценариев константные объекты и используются, включая обычные константы.


v>> Но иммутабельные данные необходимо как-то создавать.

v>> И наиболее эффективно их создавать в мутабельной манере.
v>> Я уже отсылал к паттерну mutable_builder => immutable_object.
·>Да, но там создаётся иммутабельная копия из данных мутабельного билдера. Как и в шарпах всяческих.

Есть еще несколько вариантов:

  • Данные не пересоздаются в конце работы builder, а меняют своего владельца, сам builder помечает этот факт в данных и, если построение данных продолжится после выдачи иммутабельного объекта, то builder создаст себе новую копию данных для дальнейшего над ними измывательства;

    Этот способ в дотнете мейнстримовый — так работает StringBulder и билдеры иммутабельных коллекций, т.е. сделано то предположение, что после порождения иммутабельного объекта дальнейшее построение ведется редко, поэтому оптимизирован этот сценарий;

  • Данные в конце работы builder меняют своего владельца, при этом сам builder обнуляется. Дальнейшее построение либо невозможно, либо ведётся с нуля.

    Тут происходит экономия на флагах в самих данных (флаги помечают — рассматриваются ли данные уже как иммутабельные или еще нет).

    Например, для иммутабельных деревьев в шарпе такая пометка живёт в каждом узле, что для некоторых сценариев накладно (дерево большое и является более init-only, чем CRUD). В этом случае заруливает всё и вся read-only view над данными, как показал в примере выше.


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

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

    Я попросил пример, где компилятор не даёт гарантии.

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

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

    Другой термин, бо в стандарте описаны конструкции языка, а не прикладная роль данных.


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


    На самом деле я нифига не понимаю твою логику.
    Смешались в кучу люди, кони...

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

    А вот это твоё переливание из пустого в порожнее чиста на словах/эмоциях...


    v>> Собсно, новички в плюсах зачастую маются с компиллированием программ именно из-за попыток нарушения константности.

    ·>Это, кстати, тоже минус.

    Этот минус живёт пару дней обычно у новичка.
    Затем сплошной плюс плюс. ))


    ·>С интерфейсами же всё проще — есть метод, можно дёрнуть, нет метода — нельзя дёрнуть.


    Ну... если бы существовал идеально сбалансированный ЯП, не было бы так много других. ))

    Увы, каждый язык заточен не только под конкретные сценарии, но и под конкретный образ мысли программиста, и даже под его уровень.
    В некоторые языки "порог входа" ниже, но и возможностей в некоторых задачах меньше.
    За всё надо платить...


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

    v>> К тому, что компилятор в плюсах отслеживает попытки "протечек" и отказывается компилировать такой код.
    ·>Не отслеживает. Сам же ниже пишешь, например, о сценарии утечки this из конструктора.

    Это классический побочный эффект.
    Тоже иногда сильная и нужная весчь, просто не всегда сочетается с иммутабельными сценариями.

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

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

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


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

    v>> result[{42, 43}] = "42, 43";
    ·>Здесь формально копиктор struct срабатывает.

    Здесь сам словарь мутабельный, ключ словаря иммутабельный, данные по ключу перезаписываемы.
    result[index] возвращает non-const ссылку на значение, по этой ссылке происходит присвоение нового значения.

    Кстате, operator[] для map в плюсах имеет non-const сигнатуру и это порой неудобно.
    Можно было добавить рядом аналогичную const-сигатуру и возвращать константную ссылку, потому как это сделано для метода at.

    Т.е. можно было так:
    value & operator[](const key & index) { ... }
    
    const value & operator[](const key & index) const { ... }

    Второй вариант вызывается для константных объектов, соответственно, присвоить по константной ссылке ничего нельзя (это не может быть left-value), можно только прочитать (right-value).


    v>> // но здесь иммутабельный объект

    v>> const SomeDictionary dict = buildDictionary();
    ·>И здесь формально копия.

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


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


    Это зависит от взаимного времени жизни — поток не должен пережить время жизни такой переменной (например, создали такую переменную в main и передали программе дальше).
    Или же такая переменная может быть глобальной/статической, как я и показал в примере.


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

    ·>Это немного враньё. Значение мы мутировали у result, а dict это копия.

    Формально да.
    А на деле происходили операции в одной области памяти, т.е. получился классический кейз иммутабельных данных — init-only.

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

    Работает этот трюк за счёт того, что время жизни объекта не обязательно равно времени жизни переменной.
    Именно поэтому описание init-only сценариев настолько простое/элегантное — здравствуй выразительность! ))


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

    ·>Потому что копии.

    Это может быть копия указателя, как в джаве.
    Но тогда надо озаботиться распространением константности по указателям.


    v>> ·>Допустим, у тебя есть где-то const SomeThing someValue(42); — ты гарантируешь, что someValue — иммутабельно?

    v>> Без хаков если, то да.
    ·>И если из конструктора this не утекает, например.

    Выше отписывался по этому кейзу.


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

    ·>by value это скучный случай, но т.к. там копирование, то не всегда лучший.

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


    v>> Здесь оператор new возвращает указатель на неконстантный объект, но мы его сохраняем в указатель на константный объект.

    v>> Если конструктор SomeObject не имеет побочных эффектов (не сохраняет указатель на себя где-нить в глобальной переменной, т.е. если нет протечек), то гарантии получаются железобетонные.
    ·>Это тавтология какая-то. "Если гарантии есть, то гарантии есть". Вопрос и состоит в том — откуда собственно берутся эти гарантии отсутствия побочек и протечек? Ответ — обеспечиваются программистом. Вывод —
    ·>ты словоблудишь, т.к. речь идёт о гарантиях, даваемых компилятором.

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

    Тут удобней рассуждать о "чистых функциях" (в том числе о конструкторах/деструкторах) и такие обсуждения ведутся.
    Например, в некоторые языки добавляют ключевое слово clean, которое может дать обсуждаемые гарантии отсутствия протечек.


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


    Тогда обернуть объект в read-only view.


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


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


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

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

    Это всё понятно, если самому всё с 0-ля писать (и то, вопрос выше в силе).
    Непонятно как использовать тысячи уже готовых типов в имммутабельных сценариях.

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


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

    ·>Ага. Но не получается сделать ссылку на иммутабельный объект.

    Абсолютно верно.
    Эта ссылка может ссылаться как на мутабельный, так и на иммутабельный объект.

    Следовательно, гарантии происходят от того, кто эту ссылку передаёт во внешний мир — ведь только владелец данных "знает" характер своих данных.
    А выполняемый read-only код единообразен (например, банальное логирование), отсюда ортогональность кода к данным.


    v>> ·>Это ложится на программиста, что в плюсах, что в джаве, что в шарпах. const для иммутабельности не нужен. Нужен record.

    v>> Это вам в джаве нужен, т.к. иммутабельность обеспечивается прикладной семантикой, где record — лишь синтаксический сахар для такой семантики.
    ·>Ну не совсем. В java обычно не делают что-то ради одной цели. records ввели в яп ещё и для реализации pattern matching.

    Это, скорее, следствие.
    И вообще, отдельная тема.
    В какую-то версию шарпа ввели Deconstruct для паттерн-матчинга (который матчил только туплы и рекорды, на манер которых позже сделали джавовские)... А потом допилили паттерн-матчинг до спецификации полей через is { Field1 == X }, что теперь Deconstruct стал не нужным, бгг...


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

    ·>Этот случай, кстати, поинтереснее. Т.к. это не голая структура, а есть куча всяких методов. И, внезапно, выясняется, что хоть и "резко", но уже не нахаляву — интерфейс SomeDictionary уже надо внимательно проектировать с разделением const/nonconst методов.

    Ес-но.
    Но это всё еще одно описание типа, где ты просто помечаешь read-only методы через const.

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


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

    ·>константное, а не иммутабельное. Не жонглируй терминами.

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


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

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

    Это всё до некоторой степени, пока не пошли хаки, например, через реинтерпретацию памяти или рефлексию.

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

    Любой современный мейнстрим — всегда компромисс.
    Т.е., строго говоря, мы обсуждаем лишь удобство организации этого компромисса.
    Ту самую "выразительность" и ничего более, держа в уме, что речь идёт о, таки, мультипарадигменных, т.е. слишком компромиссных ЯП.
    (да, современную джаву можно уже называть мультипарадигменной, и чем дальше, тем больше — осталось дело за малым — узаконить функциональный тип в языке, бгг)


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

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

    Разве?

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

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

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


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


    Гарантии в любом случае недоказуемы в обсуждаемых языках.

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

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

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


    v>> В общем, подход плюсов оказался настолько удобным, что его перетянули и в C# (но только для value-типов) и даже в последние версии FreePascal.

    v>> Ранее в этих языках был доступен только подход джавы.
    ·>Не очень понял, что ты имеешь в виду перетянулы в шарп?

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

    Для борьбы с потенциальными протечками локальных ссылок ввели ref struct — это такие value-типы, которые могут жить только на стеке.
    А так же ввели инструмент контроля за протечками — ключевое слово scoped.
    Более подробно расписано в доке, там достаточно интересно. ))

    Всё вместе служит той же цели — выразительности кода при достижении тех или иных гарантий.


    v>> ·>Нет, не подмножеством. read-only view для мутабельного объекта — не подмножество иммутабельного сценария, ну никак.

    v>> Еще как подмножество.
    ·>Я привёл явный пример когда это не так.

    Не видел.
    И оно не может быть "не так" согласно определению иммутабельности и семантики кода, получающего read-only доступ к данным.


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

    ·>Даже по количеству буковок не сильно отличается.

    Не скажи.))

    Я привёл примёр read-only view на шарпе (на джаве можно аналогично, разве что +1 к косвенности), а в плюсах достаточно было простого ключевого слова const безо всей этой возни.


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

    v>> В джаве тебе пришлось бы делать два типа дерева и два кода для их обхода (или выкручиваться через интерфйесы, что утяжеляет/тормозит).
    ·>Я уже об этом упоминал. Нет никакого утяжеления. Интерфейсы в яве практически бесплатны.

    Вранье. ))
    Интерфейсы в джаве намного тяжелее прямых методов.
    Бесплатны они только если компилятор или JIT "видят" жизненный цикл ссылки — тогда может заменить вызов метода через интерфейс прямым вызовом.
    В плюсах аналогично.

    Стоимость же честного вызова интерфейса в плюсах дешевле, бо в плюсах объект ссылается на свою vtable явно, а в джаве и шарпе — через промежуточный дескриптор типа, т.е. +1 к косвенности, в сравнении с плюсами.


    v>> ·>константность — не является необходимостью

    v>> Разумеется, константность не является необходимостью для иммутабельных сценариев.
    v>> Она лишь дико экономит труд програмиста и размер конечного бинаря, а так-то фигня полная.
    ·>Не экономит она труд.

    Выше пример, который не нужен в плюсах.


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


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

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


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

    v>> Наоборот, в шарпе вот относительно недавно добавили.
    v>> А потом опять добавили.
    v>> Ключевое слово чуть другое — readonly, но смысл и сценарии использования те же.
    ·>Если я правильно помню, то это аналог final-поля, который был в java с рождения.

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


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

    ·>Не понимаю что ты понимаешь под сложностью.

    Лишний код.
    Тот же твой Defensive copying как малая часть этой сложности.
    Например, в дотнете достаточно передавать DateTime по значению или по константной ссылке, а в джаве надо делать GC-копию объекта на каждый чих.
    Да там уже просто рука устаёт всё это внимательно описывать. ))
  • Отредактировано 04.09.2023 16:26 vdimas . Предыдущая версия . Еще …
    Отредактировано 04.09.2023 13:09 vdimas . Предыдущая версия .
    Отредактировано 04.09.2023 13:07 vdimas . Предыдущая версия .
    Отредактировано 04.09.2023 13:05 vdimas . Предыдущая версия .
    Отредактировано 04.09.2023 13:03 vdimas . Предыдущая версия .
    Отредактировано 04.09.2023 13:00 vdimas . Предыдущая версия .
    Отредактировано 04.09.2023 13:00 vdimas . Предыдущая версия .
    Re[91]: Когда это наконец станет defined behavior?
    От: · Великобритания  
    Дата: 05.09.23 11:16
    Оценка: -1 :)
    Здравствуйте, 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 не пнул только ленивый, там было дохрена проблем, первый блин комом. Какой-то присущей сложности нет.
    но это не зря, хотя, может быть, невзначай
    гÅрмония мира не знает границ — сейчас мы будем пить чай
    Отредактировано 05.09.2023 11:28 · . Предыдущая версия .
    Re[92]: Когда это наконец станет defined behavior?
    От: vdimas Россия  
    Дата: 19.02.24 18:49
    Оценка:
    Здравствуйте, ·, Вы писали:

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


    Именно иммутабельность я и продемонстрировал.


    ·>Или ты вообще невдупляешь что такое иммутабельность.


    Это "неизменяемость" по-русски.
    А что конкретно ты подразумеваешь под иммутабельностью — тут я уже ХЗ.
    Это уже какая-то нубская эзотерика попёрла — попытка наградить известные слова неизвестным смыслом.

    В программировании об иммутабельности данных говорят исключительно и только в связке с классами алгоритмов над неизменяемыми данными, бо сама по себе иммутальность и нафик никому не сдалась.

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


    ·>Если так, то почитай доку .NET чем отличаются IImmutableDictionary и IReadOnlyDictionary.


    ЧТД "почитай доку".
    Оба интерфейса являются прикладными с т.з. компилятора, т.е. никаких гарантий не дают.
    Гарантии там сугубо вербальные, "в документации" (С), и могут быть нарушены в реализации интерфейсов аж бегом (преднамеренно или нет), бо компилятор умывает руки.

    Учитывая, что процитированное замечание я уже делал:

    В плюсах ведь доступен подход джавы, но это ж издевательство над психикой, поэтому никто так не делает.

    ... я делаю вывод о низком уровне сообразительности собеседника.

    Ты и сам должен понимать процитированное, что Джава крайне бедна, это один из самых паскуднейших языков современного мейнстрима (хуже был только Objective-C, но уже почти полностью заменён Свифтом и чистыми плюсами) и если что-то можно сделать в Джаве, то это можно сделать почти в любом ЯП. Тебе показали альтернативные в разы более выразительные ср-ва для достижения тех же целей — но ты оказался не в состоянии их понять.

    Помнишь, я уже попросил тебя реализацию словаря или приоритетной очереди на джавовских рекордах?
    В этом месте умный собеседник резко бы осознал, что дико загоняет, прикинулся бы ветошью и не отсвечивал, бо экономия на автоматическом описании типов там вышла бы ничтожной в сравнении со всем остальным необходимым объемом работ.
    А тупишь и тупишь, в попытках оправдать милое сердцу убожество.
    Ты ведь для этого пишешь с анонимного акка, чтобы не стыдно было, верно?
    Re[82]: Когда это наконец станет defined behavior?
    От: vdimas Россия  
    Дата: 19.02.24 18:51
    Оценка:
    Здравствуйте, ·, Вы писали:

    ·>Этот сценарий может возникнуть не только вокруг константности, а вообще везде. В C++ подстелили соломку, взяв легаси от C, а интерфейсы могут покрыть любую семантику.


    Но не дать никаких гарантий, которые ты так настойчиво требуешь от коллег.
    Это уже откровенная глупость выходит ))
    Re[93]: Когда это наконец станет defined behavior?
    От: · Великобритания  
    Дата: 20.02.24 00:15
    Оценка: :)
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, ·, Вы писали:


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

    V>Именно иммутабельность я и продемонстрировал.
    Ты продемонстрировал readonly-view. Иммутабельности тут нет. В С++ иммутабельности тоже нигде нет (в лучшем случае для примитивов). Есть только readonly. В Java есть иммутабельность в виде record.

    V>·>Или ты вообще невдупляешь что такое иммутабельность.

    V>Это "неизменяемость" по-русски.
    Пример для совсем тупых: "/proc/uptime" — является read-only файлом. Он иммутабельный?
    Разберись чем отличается "неизменяемость" от "доступ только для чтения". Когда разберёшься, приходи.
    но это не зря, хотя, может быть, невзначай
    гÅрмония мира не знает границ — сейчас мы будем пить чай
    Отредактировано 20.02.2024 0:17 · . Предыдущая версия .
    Re[94]: Когда это наконец станет defined behavior?
    От: vdimas Россия  
    Дата: 20.02.24 11:31
    Оценка:
    Здравствуйте, ·, Вы писали:

    ·>Ты продемонстрировал readonly-view. Иммутабельности тут нет. В С++ иммутабельности тоже нигде нет (в лучшем случае для примитивов). Есть только readonly.


    У "неизменяемости" может быть сколько угодно синонимов и разных ключевых слов в разных ЯП — const, readonly, final и т.д.


    ·>Разберись чем отличается "неизменяемость" от "доступ только для чтения".


    Всё-таки эзотерика? ))
    Хорошо, что ты прилюдно признался.
    В угол, на гречку!

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


    ·>Пример для совсем тупых: "/proc/uptime" — является read-only файлом. Он иммутабельный?


    Это пример от тупых, ведь они понятия не имеют, как устроен этот файл.
    Вполне может быть так, что на иммутабельных типах данных унутре, что почти всегда и происходит в тех же lock-free реализациях, когда ширина типа данных больше ширины слова — тогда значением оперируют по ссылке, которую (ссылку) подменяют в безблокировочной манере, но по самой ссылке находятся неизменяемые/иммутабельные данные.

    Твой "/proc/uptime" — это может быть лишь интерфейсом к таким данным. ))

    В любом случае, неизменяемость данных означает init-only характер оперирования данными, и как ты собрался "доказать", что это не так в случае "/proc/uptime"?
    Опять поторопился и опять облажался?


    ·>Когда разберёшься, приходи.


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

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

    1. Интерфейсы не имеют к иммутабельности никакого отношения, а служат для чего и придуманы в ООП — это ср-во абстрагирования для целей последующего обобщения.

    2. Разница набора операций в IReadOnlyXXX и IImmutableXXX показывает лишь озвученную разницу — в уровне абстрагирования/обобщения, соотв. IReadOnlyXXX может быть использован в более общем коде, а IImmutableXXX в более специализированном. Плюс, не стоит забывать, что интерфейсы — это совокупность операций, представляющих собой "тип" в ООП-ЯП (см второй абзац этого поста).

    3. Набор операций IReadOnlyXXX является подмножеством IImmutableXXX (ты даже с этим пытался спорить, не посмотрев на граф наследования), но оба интерфейса к иммутабельности не имеют никакого отношения, когда речь заходит о гарантиях. Бо интерфейсы (абстрактные классы) в классическом ООП используют не для гарантий, а как единственный доступный механизм построения и оперирования абстракциями. Остальное — спекуляции полуграмотных полупрограммистов из цикла "слышал звон".

    4. Неизменяемость ортогональна любому выбранному уровню абстракции, прекрасно умеет жить как без абстракций, так и сосуществовать с ними.

    5. Любые гарантии/ограничения, которые предоставляет ЯП для решения тех или иных классов задач, намного лучше отсутствия таких ср-в.

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

    Но я тебя слегка утешу — в жабке есть GC, который является достаточно эффективной базой для реализации большого класса алгоритмов над неизменяемыми данными.

    Поэтому, если бы ты хоть что-то понимал в таких алгоритмах, тебе достаточно было ткнуть в отсутствие GC в C++ и одновременно в происходящее во многих таких алгоритмах (например, алгоритмы над иммутабельными сбалансированными деревьями и вообще над иммутабельными версионными графами, известными еще со времён самого первого Лиспа), где наличие GC упрощает реализацию таких алгоритмов.
    Отредактировано 20.02.2024 13:56 vdimas . Предыдущая версия . Еще …
    Отредактировано 20.02.2024 13:56 vdimas . Предыдущая версия .
    Отредактировано 20.02.2024 13:54 vdimas . Предыдущая версия .
    Отредактировано 20.02.2024 13:54 vdimas . Предыдущая версия .
    Отредактировано 20.02.2024 11:34 vdimas . Предыдущая версия .
    Re[11]: Когда это наконец станет defined behavior?
    От: Кодт Россия  
    Дата: 21.02.24 10:25
    Оценка:
    Здравствуйте, T4r4sB, Вы писали:

    TB>Ну уб получается.


    У, но другое. Не undefined, а unspecified.
    Вход-выход в функцию (а с c++17 даже передача каждого отдельного аргумента) создаёт точки следования, поэтому есть какие-то базовые гарантии о неповреждённых значениях.
    Но вот порядок вызовов функции и обращения к переменной — произвольный.

    https://en.cppreference.com/w/cpp/language/eval_order

    int a = 1;
    
    inline int g() { return ++a; }
    
    int f1a() { return g()*100 +  g() *10 +   a  ; }  // unspecified
    int f1b() { return  a *100 +   a  *10 +   a  ; }  // unspecified
    int f1c() { return  a *100 +   a  *10 +  g() ; }  // unspecified
    int f1c() { return  a *100 +   a  *10 + (++a); }  // UNDEFINED
    
    int f2a() { return g()*100 +  g() *10 +  g() ; }  // unspecified
    int f2b() { return g()*100 + (++a)*10 +  g() ; }  // unspecified
    int f2c() { return g()*100 + (++a)*10 + (++a); }  // UNDEFINED
    Перекуём баги на фичи!
    Re[93]: Когда это наконец станет defined behavior?
    От: Кодт Россия  
    Дата: 21.02.24 10:28
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Именно иммутабельность я и продемонстрировал.


    и некропостинг...
    Перекуём баги на фичи!
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.