Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Когда "замыкания" "захватывают контекст", создаётся структура в какой-то памяти, и эту память надо в последствии освободить. ЭФ>Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ?
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Когда "замыкания" "захватывают контекст", создаётся структура в какой-то памяти, и эту память надо в последствии освободить. ЭФ>Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ?
В языке Swift для MacOS и гейфонов, например, используются замыкания: https://swiftbook.org/docs/zamykaniya/
Он компилируется в нативный код, и никакого GC не содержит (для управления памятью применяется автоматический reference counting).
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Здравствуйте, vsb, Вы писали:
vsb>В Java и сейчас так (та ещё дурость).
У меня на рабочем проекте (OpenJDK 21) компилируется без ошибок:
private void filterBy_SubjectTypeCode_Inn(@NonNull TARLSubjectInfoGetListInParam subjectSet,
@NonNull List<GoldSubjectRecord> goldSubjectRecords) {
if (subjectSet.getCommon() != null) {
Integer idSubjectType = idTypeCacheService.getIdType(subjectSet.getCommon().getSSubjectTypeCode());
String sInn = subjectSet.getCommon().getSInn();
if (idSubjectType != null && sInn != null) {
mdmDataManageService
.getTarantoolObjectByField("sINN", sInn, GoldSubjectRecord.class)
.stream()
.filter(record -> record.getIdSubjectType().equals(idSubjectType))
.forEach(goldSubjectRecords::add);
}
}
}
Раньше обязательно требовало final для таких переменных.
vsb>Поле неизменяемое, а содержимое изменяемое.
В Java полно подобных косяков, к сожалению. Язык из костылей, прямо аналог Linux в мире ЯП. Но там как раз GC, разруливающий циклические зависимости.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Здравствуйте, Worminator X, Вы писали:
vsb>>В Java и сейчас так (та ещё дурость).
WX>У меня на рабочем проекте (OpenJDK 21) компилируется без ошибок: WX>
WX>Раньше обязательно требовало final для таких переменных.
Сейчас тоже требует. Просто это называется effectively final. Типа писать не обязательно, но оно подразумевается, менять ты её не можешь. Это ещё одна дурость.
vsb>>Поле неизменяемое, а содержимое изменяемое.
WX>В Java полно подобных косяков, к сожалению. Язык из костылей, прямо аналог Linux в мире ЯП. Но там как раз GC, разруливающий циклические зависимости.
Так в 99% языков нет понятия константности. И в 1% эта константность порой на уровне "я обещаю" и легко её убрать.
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Когда "замыкания" "захватывают контекст", создаётся структура в какой-то памяти, и эту память надо в последствии освободить. ЭФ>Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ?
Замыкания аналогичны созданию класса с конструктором, который принимает элемент замыкания в качестве аргумента, поэтому вопрос про GC не совсем корректен. Подсчёт ссылок нельзя использовать в том случае, когда возможны циклические связи, но с замыканиями это никак не связано. Например есть контекст A, внутри которого есть переменная B и создаётся замыкаение С, с захватом переменной B. Получаются связи A -> B, A -> C, C -> B, цикла нет. Но если, например, замыкание захватывает сам контекст, то тут уже явная цикличность, и без сборки мусора не обойтись.
Здравствуйте, vsb, Вы писали:
vsb>С замыканиями очень просто создать циклическую ссылку. Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще.
Обычно для этого захватываемые переменные (поля объекта-замыкания) делают неизменяемыми, например, такое долгое время было в Java.
В результате присвоить им значение функции с замыканиями нельзя, и циклические ссылки невозможны.
Там, где зачем-то возможны (сложно придумать такую задачу), скорее всего, без GC не обойтись.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Здравствуйте, vsb, Вы писали:
vsb>Сейчас тоже требует. Просто это называется effectively final. Типа писать не обязательно, но оно подразумевается, менять ты её не можешь. Это ещё одна дурость.
Это не дурость, а минимизации wtf/LoC. В шарпе и js с этим налажали и там это классическая грабля.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, vsb, Вы писали:
vsb>Дурость, натуральная. vsb>Во-первых никакой необходимости в этом всём вообще нет. Заменяем int x на int[] x = new int[1], все обращения к x на x[0] и готово, у нас с абсолютно тривиальным преобразованием работает изменение переменной из замыкания.
Необходимость не техническая, а семантическая. Чтобы по коду было интуитивно понятно что происходит. Замена значения на стеке полем в объекте — имеет конкретные последствия и работает единообразно для всего, никаких особых случаев для лямбд нет.
vsb>Во-вторых, даже если разработчики Java с чего-то решили, что теперь их миссия — zero overhead abstractions (ха-ха) и превращать под капотом переменную в массив это не ок, то никакой проблемы написать final у нужной переменной нет и это как раз уменьшает WTF/LoC. Т.е. необходимости ввода effectively final концепции не было никакой. Нужна тебе final переменная — ну и напиши final. Это как вводить effectively int концепцию — если не указали у переменной тип, то она будет считаться int-ом. Зачем? Какую проблему решает? Непонятно.
effectively final — небольшой сахарок, позволяющий выводить final автоматически во время компиляции и возможность его не писать его явно. В каком-то смысле аналог var.
>> В шарпе и js с этим налажали и там это классическая грабля. vsb>Это вообще другая проблема и эту проблему так же легко исправить.
Проблема в том, что неоднозначно что в какой момент должно меняться. Поэтому явное требование финальности — заставляет писать код, в котором всё становится явным.
vsb>В Go, кстати, исправили в одной из последних версий.
Любопытно, как?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Когда "замыкания" "захватывают контекст", создаётся структура в какой-то памяти, и эту память надо в последствии освободить.
Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ?
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ?
Если говорить об автоматическом котроле жизни захваченных ссылок,
то стандартная проблема Reference Counting в том, что можно суметь сделать циклическую ссылку.
С замыканиями тоже — ссылку на замыкание можно поместить либо напрямую в захваченный объект, либо сделать более косвенный круг ссылок.
Если о не автоматическом, как в C++, то можно захватить shared_ptr или weak_ptr по значению, и будет Reference counting.
Разумеется, отвественность за отсуствие циклических ссылок в этом случае на авторе.
С замыканиями очень просто создать циклическую ссылку. Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще.
Здравствуйте, Worminator X, Вы писали:
vsb>>С замыканиями очень просто создать циклическую ссылку. Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще.
WX>Обычно для этого захватываемые переменные (поля объекта-замыкания) делают неизменяемыми, например, такое долгое время было в Java.
В Java и сейчас так (та ещё дурость).
WX>В результате присвоить им значение функции с замыканиями нельзя, и циклические ссылки невозможны.
Поле неизменяемое, а содержимое изменяемое.
final var reference = new AtomicReference<>();
Runnable runnable = () -> System.out.println(reference.get());
reference.set(runnable);
WX>Там, где зачем-то возможны (сложно придумать такую задачу), скорее всего, без GC не обойтись.
Здравствуйте, Worminator X, Вы писали:
ЭФ>>Когда "замыкания" "захватывают контекст", создаётся структура в какой-то памяти, и эту память надо в последствии освободить. ЭФ>>Достаточно ли для этого применять Reference Counting или обязательно потребуется полномасштабная Garbage Collection ? WX>В языке Swift для MacOS и гейфонов, например, используются замыкания: https://swiftbook.org/docs/zamykaniya/ WX>Он компилируется в нативный код, и никакого GC не содержит (для управления памятью применяется автоматический reference counting).
Ичсх чуда не получается: https://medium.com/@muzammalshahzad494/understanding-memory-leaks-caused-by-closures-in-swift-fd4358128d84
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
vsb>>Сейчас тоже требует. Просто это называется effectively final. Типа писать не обязательно, но оно подразумевается, менять ты её не можешь. Это ещё одна дурость. ·>Это не дурость, а минимизации wtf/LoC
Дурость, натуральная.
Во-первых никакой необходимости в этом всём вообще нет. Заменяем int x на int[] x = new int[1], все обращения к x на x[0] и готово, у нас с абсолютно тривиальным преобразованием работает изменение переменной из замыкания.
Во-вторых, даже если разработчики Java с чего-то решили, что теперь их миссия — zero overhead abstractions (ха-ха) и превращать под капотом переменную в массив это не ок, то никакой проблемы написать final у нужной переменной нет и это как раз уменьшает WTF/LoC. Т.е. необходимости ввода effectively final концепции не было никакой. Нужна тебе final переменная — ну и напиши final. Это как вводить effectively int концепцию — если не указали у переменной тип, то она будет считаться int-ом. Зачем? Какую проблему решает? Непонятно.
> В шарпе и js с этим налажали и там это классическая грабля.
Это вообще другая проблема и эту проблему так же легко исправить. В Go, кстати, исправили в одной из последних версий.
Кстати, это, конечно, отстой, что go не парится о backward compatibility и позволяет себе изменения, которые могут вызвать незаметные изменения в поведении кода после смены версии компилятора. В java такого вроде не припомню.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, vsb, Вы писали:
vsb>С замыканиями очень просто создать циклическую ссылку.
Не проще, чем с любыми другими объектами, т.е. в те же минимум два действия — сначала сохраняем некий ref-count объект в замыкании, а потом полученное замыкание сохраняем в поле захваченного объекта.
Т.е., тут нет никакого доп. способа "автоматического" создания ref-count ссылок.
vsb>Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще.
Это несильно отличается от явного вызова Dispose() в том же дотнете.
Здравствуйте, vdimas, Вы писали:
vsb>>Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще.
V>Это несильно отличается от явного вызова Dispose() в том же дотнете.
Что такое Dispose? Я так понимаю, аналог close в Java для закрытия ресурсов вроде файлов?
Тогда — это сильно отличается. Потому, что динамическая память — она везде, она в каждой функции практически. Во многих местах неявно, создали замыкание, оно забрало переменные в кучу. И очень часто происходит ситуация, когда создаётся объект в одном месте, а становится доступен для удаления — в другом (причём зачастую это вообще статически не определяется).
С файлами и подобными ресурсами всё по-другому. Они действительно практически всегда открывается и закрываются в одном методе. И для них этот паттерн прекрасно работает.
Поэтому одно другому не мешает. Нужна и сборка мусора, нужен и try-finally или его аналог.
Здравствуйте, vsb, Вы писали:
vsb>>>Reference counting будет работать, но нужно не забывать руками рвать циклы. С GC это всё, конечно, намного проще. V>>Это несильно отличается от явного вызова Dispose() в том же дотнете. vsb>Что такое Dispose? Я так понимаю, аналог close в Java для закрытия ресурсов вроде файлов?
Что-то типа, но только оно максимально распространено в дизайне.
Более того, в C# введены ср-ва RAII (через ключевое слово using), которые доказали свою мега-удобность еще в плюсах.
Т.е., язык C# "знает в лицо" конкретный интерфейс IDisposable() или даже просто метод Dispose() у ref-структур, которые не могут реализовывать интерфейсы.
vsb>Тогда — это сильно отличается. Потому, что динамическая память — она везде, она в каждой функции практически. Во многих местах неявно, создали замыкание, оно забрало переменные в кучу. И очень часто происходит ситуация, когда создаётся объект в одном месте, а становится доступен для удаления — в другом (причём зачастую это вообще статически не определяется).
И в чём печаль? ))
Разумеется, задачи бывают разные — иногда жизненный цикл объектов отследить просто (через граф владения), а иногда сложно (расшаренное владение, в т.ч. по потокам исполнения).
Не зря же, что в Джаве, что в Шарпе в некоторых задачах не стесняются использовать подсчёт ссылок.
Да и сам грешен на Шарпе этим порой. ))
И вот как раз через паттерн Dispose() удобно обыгрывается подсчёт ссылок, когда задача это диктует.
vsb>С файлами и подобными ресурсами всё по-другому. Они действительно практически всегда открывается и закрываются в одном методе. И для них этот паттерн прекрасно работает.
Ресурсом может быть не только файл.
Сейчас всё такое сетецентрическое...