Как стать автором
Обновить

Комментарии 205

когда очередная фишка или функционал что запланировал - доделано или работает, а работает значит нифига не заметно/ничего не поменялось, но было желание и изучение - это праздник. когда матеша заработала, реально обрадовался, я её долго смотрел тестил, пробовал/понимал

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

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

про шаблоны тоже заметил, нужен баланс шаблонов/не шаблонов

это open source,

давайте контрибьютить, если хотим модули

Нет, вы совсем не правы.

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

Отсутствие всех новых фичей на релизе - это стандарт современного ведения проектов.

Согласен.

А если бы в плюсы завезли паттерн-матчинг и увезли как можно больше UB - я был бы совсем рад.

(он пожимает плечами)

окэй, попробую убедить фактами. UB позволяет компиляторам генерить более эффективные бинари.

Что будет, если UB убрать из языка?

Если вместо UB при выходе за границы массива принудительно добавить bound checks, код станет на 0.3% менее шустрым (в среднем). Тыц.

Много это или мало? Для HFT, highload сервисов на ограниченных ресурсах и тд и тп - это дофига.

bound checks - лишь один из сотен видов UB. Что будет, если заменить многие из них на безопасную, но избыточную обработку? Это замедлит код не на 0.3%, а гораааздо сильнее.

Но C++ - это в первую очередь про эффективность. Потеряв UB, мы потеряем и в эффективности.

сохранённый ответ

Ub далеко не всегда равно доп проверкам

Скажите честно — если бы вы не знали о том, что это — C++, смогли бы вы узнать язык, на котором это написано? Мне больше нечего добавить.

И зачем так писать код? Как это отлаживать? Потом придет новый программист и будет пробираться через все эти переопределения и лямбды? Со временем начинаешь понимать, что процедурный стиль и легче в отладке и в поддержке.

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

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

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

Причём она прямо следует из принципа инкапсуляции

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

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

когда объявленные локальными переменные в функции улетают непонятно куда с лямбдой это по вашему принцип инкапсуляции?

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

У меня есть функция

find :: (a -> Bool) -> [a] -> Maybe a

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

OFF. Ты знаешь, что можно почитать по применению теории типов в народном хозяйстве?

К примеру, я хочу написать CAD для моделирования электрических схем какого-то сорта, но я хочу сделать 2 уровня моделирования, первый - грубый, чтобы отсекать заведомо негодные схемы до отправки на расчёт. Это - типичная задача для типов. Вопрос - есть ли книжки, где подобное разбирается? Это Domain Driven Design made functional?

То есть, если мы пользуемся Кузошным подходом (framework) - абстрактной интерпретацией, то мы можем электрическую схему рассматривать как программу для расчётного кода, а расчётный код - как интерпретатор. Далее, никто же не запрещает делать более грубый интерпретатор, совместимый с расчётным кодом. Это будет что-то, подобное статической проверке типов.

К примеру, я хочу написать CAD для моделирования электрических схем какого-то сорта, но я хочу сделать 2 уровня моделирования, первый - грубый, чтобы отсекать заведомо негодные схемы до отправки на расчёт. Это - типичная задача для типов.

Если это не DSL для описания схем, то типы тут не очень помогут.

То есть, ты, конечно, можешь вычислительное ядро своего CAD'а обложить типами, чтобы оно не принимало некорректные задачи, но это всё равно потребует кода по «валидации» пользовательских схем (пусть даже мы валидируем в строго типизированные значения), и никто не помешает описать некорректные пользовательские схемы.

Если же ты хочешь делать DSL, то настоятельно рекомендую почитать algebra-driven design Сэнди Магвайра. Это не совсем про типы, но достаточно близко.

Насчёт непосредственно использования сильных типов в подобных задачах не для академиков я ничего сходу вспомнить не могу. Книжки уровня software foundations — перебор и слишком базовые (это по факту учебник по coq'у, на твой вопрос оно не факт что ответит), условные научные статьи с условного ESOP, как люди там типами проверяют ширину квантовых схем, например — это, ну, научные статьи, а не для народного хозяйства, и люди там придумывают свою очередную систему типов, в хаскеле/идрисе ты такое не запрогаешь (хотя там что-то было про квантовую либу для идриса — могу найти, если интересно).

Если это не DSL для описания схем, то типы тут не очень помогут.

Я, видимо, не очень понятно написал - разумеется, DSL. У нас вообще победивший постмодерн в CompSci - всё есть текст, а в компьютере нет ничего, кроме языков и транзисторов.

потребует кода по «валидации» пользовательских схем

Если этот код по валидации правильно написан (см. правила вывода типов Х-М), мы его можем использовать прямо на этапе построения, запрещая делать связи между неподходящими для этого элементами. Гипотетически, запрещая соединять слаботочку и высоковольтную части. Или, как в радиоприёмнике - тракт "постоянного тока" и тракт до выпрямителя.

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

Если же ты хочешь делать DSL, то настоятельно рекомендую почитать algebra-driven design Сэнди Магвайра. Это не совсем про типы, но достаточно близко.

Спасибо!

хотя там что-то было про квантовую либу для идриса — могу найти, если интересно

Я подозреваю, что это не совсем то.

Я, видимо, не очень понятно написал - разумеется, DSL.

Тогда да, ADD Магвайра, и формально доказывать выписанные для комбинаторов законы (и требовать в типах комбинаторов нужный контекст, если оно вылезает из законов).

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

Попытаться опубликовать статью и посмотреть на реакцию ревьюверов :]

Я подозреваю, что это не совсем то.

ХЗ, но я всё равно нашёл: https://arxiv.org/pdf/2111.10867

Думаю, что оно может оказаться релевантнее, чем кажется сходу.

Вообще, я тут понял, что в таких задачах (универсальные расчётные системы вроде CAD) переключение взгляда на "исходные данные = программа" даёт очень многое:

1. Пирамиду:
интерпретация (оно же расчёт) - статический анализ - общий случай абстрактной интерпретации - система типов

2. Можно думать про наши хорошо проработанные методики работы с программами, начиная с VCS.

3. Дальше можно обдумывать, что же означают проекции Футамуры и суперкомпиляция.

чтобы отсекать заведомо негодные схемы до отправки на расчёт. Это - типичная задача для типов. Вопрос - есть ли книжки, где подобное разбирается?

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

Я передаю туда лямбду, которая из текущего скоупа захватывает, например, хэшмапу и проверяет вхождение в неё. Где здесь что куда утекает и ломает инкапсуляцию?

Тут ничего не утекает, но "ад коллбеков" тоже ведь существует.

У вас, хаскеллистов, есть более занятная штука, когда замыкания реально утекают, но программа, тем не менее, остаётся вполне обозримой. Эти замыкания торадиционно называются action.

Поэтому, на самом деле, при должной культуре программирования даже некоторые утекающие анонимные функции не составляют проблемы. Но работать надо в определённой культуре и поддерживать дисциплину внутри этой культуры.

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

Тут ничего не утекает, но "ад коллбеков" тоже ведь существует.

То, что этим обычно называют — почти синатксическая проблема, что код съезжает сильно вправо.

Я не представляю, что надо делать, чтобы получить ад коллбеков в вызове find.

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

Это ж gnuplot!

В правильных классах типов не очень важно, что именно вызывается. Если я дёргаю length, то неважно, какой именно Foldable мне пришёл (с некоторыми условиями типа действительно полиморфного кода).

Это ж gnuplot!

Ну не до такой степени, но повбывавбы.

В правильных классах типов не очень важно, что именно вызывается. Если я дёргаю length

Именно. Я, собственно, об этом и пейсал, что нужна культура и дисциплина. А иначе получается аналог ООПшного BaseObject::build(), при энтепрайзном дереве наследования от этого BaseObject. И в результате, при попытке понять, что же там происходит на prod сервере за DMZ, чувствуешь себя как Кай, у которого лишь кубики с буквами О, П, Ж, А и задание составить слово вечность.

Где здесь что куда утекает и ломает инкапсуляцию?

вы хотите частным случаем опровергнуть общее утверждение: что лямбды с замыканием можно использовать для нарушения инкапсуляции?

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

Но можно и проще (для вас): покажите ФП-пример (раз мы тут ФП обсуждаем), где это ломает инкапсуляцию.

Но можно и проще (для вас): покажите ФП-пример (раз мы тут ФП обсуждаем), где это ломает инкапсуляцию.

начать надо с того что я еще ни разу не видел определения инкапсуляции для ФП, может там и ломать то нечего?

Инкапсуляция — это сокрытие иррелевантных деталей реализации. ООП-модификатор private — это просто частный случай.

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

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

Вряд ли на таком определении можно строить какую-то объективную теорию.

А у нас нет объективной теории ООП, инкапсуляции, и тому подобных вещей. Любые попытки построить такую теорию разбиваются о неприменимость её на практике.

Ну или давайте с другой стороны. Что такое по-вашему инкапсуляция?

По сути вы подменили одно умное слово другим умным словом, а это даже как-то не красиво. В таком направлении дискуссия мне не интересна.

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

Что такое по-вашему инкапсуляция?

Инкапсуляция — это сокрытие данных, методов, логики,... любых деталей реализации, которые обеспечивают внешнее поведение объекта в соответствии с конечным типом (типом с которым объект был создан) этого объекта.

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

написание в ФП-стиле

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

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

И логики нет, видимо.

Данные — есть, есть просто ваши ООП-данные и функции.

Методы — это просто функции.

Поведение — это то, что функции делают.

Разделение типов создания и использования — есть (хоть через параметрический полиморфизм, например, хоть через явные преобразования).

Объекты — тоже есть, любые типы.

мне не нужны ограничения ФП

Не нужно легче рассуждать о коде и его поведении? А что вы такое пишете, если не секрет?

А что вы такое пишете, если не секрет?

так в профиль посмотрите. Пишу что угодно, но конкретно сейчас вот в этой области решения изобретаю: https://github.com/LibreOffice/core

Мне делать нужно. Рассуждать я могу хоть проще, хоть сложнее, это не проблема.

Мне делать нужно.

А, ну классическое «некогда думать, делать надо».

Рассуждать я могу хоть проще, хоть сложнее, это не проблема.

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

А, ну классическое «некогда думать, делать надо».

Передергиваете. Опять не красиво с вашей стороны: где я написал слово некогда?

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

Передергиваете. Опять не красиво с вашей стороны: где я написал слово некогда?

Как ещё интерпретировать ваше «мне делать нужно» как очевидно отрицающий мой поинт ответ?

Если например ваше легаси компилируется 2 часа, времени думать некуда девать даже если всего два раза в день надо скомпилировать.

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

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

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

Комментарии вообще описываются случайными блужданиями. А значит, чем больше размерность, тем менее вероятно, что комменты вернутся к исходной точке. :-)

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

Общего у моего вопроса и вашего ответа только слово в синтаксическом смысле «рассуждать». Даже семантика у него разная в этих случаях: я говорю о том, что «смотрю на кусок кода и пытаюсь понять, что он делает и чего он не делает», вы говорите о том, как бы сделать компиляцию всего проекта побыстрее, если он компилируется слишком долго.

Согласен с тем что С++ это офигенный ЯП. Изучение программирования начинал на Python, думал на нём же и буду работать. Как мне кажется я его не плохо изучил но никуда не брали а параллельно работал в асу и писал pet проектики на python. Пока по приколу не откликнулся на вакансию С++/qt в своём провинциальном городке и меня с нулевым опытом на С++ взяли "попробовать". Прошло 2 года и я не собираюсь слазить с этого языка, python теперь для чего нибудь быстренького, маленького проектика. А вот С++ нужно закатывать рукава и получать удовольствие. Так что да, С++ это чума!)

Не понял, почему автор считает, что лямбды в C++ лучше, чем в питоне? Хотелось бы какой-нибудь аргумент.

На мой взгляд, в питоне – нормальные классические лямбды из ФП, а в C++ этим словом называется какое-то дитя противоестественной связи ФП и ООП.

на сколько хватает моих скромных знаний лямда - это анонимный класс на месте вызова лямбды подставляется адрес того класса который вызываем, из нескольких лямбд можно сделать +- нужное навесное поведение, в своё время мне такое ДжиПиТи подсказал я изучил тему и запомнил, тоесть грубо говоря можно квесты лепить на лямбдах тоесть регистрировать в вектор обработчика список квестов и гдето во время исполнения чтото проверять, по-сути лямбда тоже functional программирование тоесть можно и в std::functional засунуть могу пример показать если интересно

class ValidRules {
public:
  int id;
  std::function<bool()> getRule;//everyRule/everyQuest
  const char* message;
};
bool checkSomethink(CGame<Component<C*>> Somethink,std::vector<int> pT,int c);
  //table of quests
  std::vector< ValidRules  > validQuests = {
    {0, [&]() -> bool { return checkSomethink(Somethink); }, "Message"}
  };

Всё так и есть в C++. Но в функциональном программировании лямбда – это просто обычное тело функции (анонимное, поскольку [пока] оно не присвоено в качестве значения какому-то имени). Так и в питоне, оператор def для объявления обычной функции является просто синтаксическим сахаром для оператора присваивания, в правой части которого находится лямбда (тело функции).

Класс нужен лямбде в C++ просто по той причине, что в силу используемого в C++ ручного управления памятью лямбде без этого класса (т.е. структуры) негде было бы сохранять свой контекст. То есть это затычка для управления памятью.

Поэтому зачем вообще нужны лямбды, я сам могу прочесть лекцию на несколько часов, а вот почему автор считает, что лямбды в С++ лучше лямбд в питоне – для меня загадка.

ну например вот я смотрел туториал "Creating a Voxel Engine (like Minecraft) from Scratch in Python" давно еще, там крутые штуки питона используются, но по итогу мощи не хватало и он врубал jit, а по самому питону не скажу

 а вот почему автор считает, что лямбды в С++ лучше лямбд в питоне – для меня загадка.

мне кажется довольно очевидным, что в питоне писать лямбды сложнее lamda x: x + 1 смысла нет, ибо код преватится в лапшу заметно быстрее, чем то же произойдёт в С++. Ещё и PEP-ом потом тыкать станут, мол не там пробел, не там запятая, плюс захват контекста имеет свои нюансы.

Разве в питоне лямбды не однострочные? Точнее, тело лямбды должно представлять собой выражение. Не так?

Согласен, в таком аргументе есть смысл.

Простая причина: многострочность без `\`. Мы пишем лябда-предикаты и пробрасываем в final-объекты функторы НЕ для того, чтобы написать `lambda a: fun(a, global_b)`, а потому что хотим:

  1. облегчить чтение кода, установив определение там, где сущность разово используется,

  2. хотим освободить память от функтора, даже если он захватил состояние из observer-сущности, вместе с завершением {}-блока кода, потому что он выражает весь необходимый алгоритм. Мне кажется, это нормально, когда лямбда может занимать 20 строк, фильтровать коллекцию и рассылать уведомления, если это разовое событие которое не должно длительно храниться в памяти. `[] () {}` (можно сократить) позволяет его оперативно уничтожить в памяти.

Думаю, что автор имеет в виду проблемы с захватом переменных замыканиям в циклах в Python. В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться. Ну и надо делать ссылку на авторский стиль.

Так ведь эти режимы захвата придуманы только из-за того, что в C++ нет сборки мусора и надо руками регулировать время жизни объектов. В питоне лямбда образует замыкание со всеми лексически видимыми переменными, так что всё работает автоматически.

Значит про проблемы вы немного не в курсе.

Возможно. Вот и хотелось бы узнать.

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

Точно так же будет работать и обычная, именованная функция. В соответствии с лексической областью видимости имён.

Вы наверное что-то мне пытаетесь доказать, но я не понимаю зачем. Хотите поспорить - напишите автору (автор в отличие от нас с вами написал свою первую книгу по питону 10+ лет назад).

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

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

Они работают ожидаемо, если люди понимают, что такое лексическое замыкание и позднее связывание. У нас на собесах 8 из 10 человек про это не знаю, а код такой писать иногда приходится (чуть сложнее, чем передать индекс в лямбду) и рецепты надо помнить. Ваше высказывание про логично/нелогично приводит меня к мысли, что вы с подобным не сталкивались. Люди вон считали это багами - Issue 13652: Creating lambda functions in a loop has unexpected results when resolving variables used as arguments - Python tracker .

В С++ аналогичной проблемы не будет если лямбда связывается по значениям, а не по ссылкам.

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

Посчитал сейчас статистику по последней программе, за которую получал деньги – 2694 строки, среди них 69 лямбд. Это достаточно для того, чтобы считать, что я сталкивался с лямбдами, или должна быть плотность функционального программирования больше?

позднее связывание

Время связывания вообще не имеет отношения к обсуждаемому вопросу.

У нас на собесах 8 из 10 человек про это не знаю

Охотно верю, но надо просвещать людей.

Люди вон считали это багами

Ну так привыкли к C++.

Ну посчитайте сколько у вас лямбд внутри цикла.

Позднее связывания к этом относится напрямую.

Да причём здесь связывание? Позднее связывание говорит только о том, что связывание производится по мере обращения к значению. Но само значение переменной i в вашем примере в любом случае к моменту вызова функции выходит за цикл, независимо от того, рано произошло связывание с ним или поздно.

Оттого, что произошло лексическое связывание, само значение же не прекращает меняться во внешней программе.

Связывание и захват значения в C++ – это разные вещи.

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

Например, вот это – позднее связывание:

i = 1
x = lambda : i()

def i ():
  return 2

print (x())

С ранним связыванием получили бы ошибку во второй строке.

А вот в такой программе:

i = 1
x = lambda : i
i = 2

print (x())

– наплевать, раннее связывание с i или позднее, всё равно значение i в момент вызова лямбды равно 2, потому что никакой другой переменной i нет.

Вот вам два примера:

lambdas = []
for idx in range(10):
    idx_copy = idx
    lambdas.append(lambda : print(idx_copy))

for l in lambdas:
    l()
using System;
using System.Collections.Generic;


namespace HelloWorld
{
	public class Program
	{
		public static void Main(string[] args)
		{
		  var lambdas = new List<Action>();
		  
		  for (var idx = 0; idx < 10; idx++)
		  {
		    var idx_copy = idx;
		    lambdas.Add(() => Console.WriteLine($"{idx} -> {idx_copy}"));
		  }
		  
		  foreach(var l in lambdas)
		    l();

		}
	}
}

Выбрал C# потому что ранее вы говорили про сборку мусора.

Я не знаток C#, но здесь у вас разница не во времени связывания, а в том, что в первом случае у вас используется обычная функциональная лямбда, т.е. просто записанное безымянное тело функции, а во втором – в цикле создаётся новый объект, содержащий функцию. Имеющий в том числе и свою собственную память.

Вот это выражение, как я понимаю, преобразуемое к Action:

() => Console.WriteLine($"{idx} -> {idx_copy}")

– конструктор объекта.

Вот в этом вот фишка: List<Action>

Сделайте в C# список указателей на функцию, и будет всё так же как в питоне.

Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.

Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.

Это-то как раз очевидно: потому что на каждой итерации создаётся отдельный экземпляр idx_copy, а вот экземпляр idx один на все итерации.

Понятно. Тут уж моих ничтожных познаний в C# не хватило. Выходит, там динамическая область видимости, а не лексическая?

В питоне тоже на каждой итерации создаётся лямбда-объект. Можете легко это проверить с помощью print(id(l)) во втором цикле.

Я не знаток C#, но здесь у вас разница не во времени связывания, а в том, что в первом случае у вас используется обычная функциональная лямбда, т.е. просто записанное безымянное тело функции, а во втором – в цикле создаётся новый объект, содержащий функцию. Имеющий в том числе и свою собственную память.

У вас опять какие-то своеобразные представления о происходящем. В питончике у лямбд тоже вполне себе есть, выражаясь вашим языком, "своя собственная память". Например, что, по-вашему, выведет вот такой код и почему?

def bar(i):
    return lambda : print(i)

def foo():
    result = []
    for idx in range(10):
        result.append(bar(idx))
    return result

lambdas = foo()
for l in lambdas:
    l()

Список 0..9, а в чём подвох? Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:

lambda : print (0)

lambda: print (1)

...

lambda: print (10)

которые помещаем в элементы списка lamdas при помощи функции foo.

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

Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:

[...]

Ни одна из этих лямбд не имеет ни собственных переменных, ни обращается к чужим на этапе применения.

Здорово вас плющит. Теперь с этой точки зрения объясните, пожалуйста, результат выполнения такого кода:

class Data:
    i = 0
    def __init__(self, i):
        self.i = i

def bar(d):
    data.append(d)
    return lambda : print(d.i)

def foo():
    result = []
    for idx in range(10):
        result.append(bar(Data(idx)))
    return result

data = []
lambdas = foo()

for l in lambdas:
    l()

print("---")

for d in data:
    d.i = d.i + 1

for l in lambdas:
    l()

А здесь в лямбда-выражение входит константное обращение к полю i объекта d. Но поскольку объекты в питоне передаются по ссылке, то константой является сама ссылка, а внутри объекта значение поля можно менять.

Обращайтесь, если что.

Какой-то набор слов. В предыдущем случае была якобы (по вашим словам) нагенерирована куча лямбд, печатающих константы 0, 1, и так далее, почему же эта куча лямбд не была нагенерирована в этом случае?

В этом случае тоже нагенерирована куча лямбд. Каждая из них содержит константную ссылку на свой экземпляр класса Data, из которого выбирает поле i. А в первом случае лямбды содержали числа непосредственно, потому что в питоне так устроена передача параметров разных типов.

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

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

Функция print принимает параметр. И функция bar принимает параметр. И в первом случае это целое число, которое является значением формального параметра i и передаётся по значению, а во втором случае – ссылка на объект d с дальнейшей операцией получения поля объекта. При этом в питоне позднее связывание, и значение выражения операции доступа к полю .i не вычисляется при вычислении лямбда-выражения.

Подставьте вместо .i в явном виде функцию-геттер get_i(), и вам будет понятнее, что происходит.

Поля объекта в питоне не являются просто пассивными смещениями в структуре, как в С++.

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

Нет, не приходит:

1) это не соответствует теории;

2) это фактически неверно. Функциональные значения (в том числе тела именованных фукций и лямбда-выражения) представляет в CPython так называемый code object, который не является объектом в смысле ООП. Хотя у него есть контейнер в виде класса;

3) код в питоне может меняться динамически, в том числе и тип свободных переменных в общем случае неизвестен во время вычисления лямбда-выражения, как я показывал выше. Да и структура классов тоже. Этим я хочу сказать, что класс d может просто-напросто ещё не существовать в момент вычисления лямбды, обращающейся к нему.

1) это не соответствует теории;

"Если факты не соответствуют теории, то тем хуже для фактов"? :)

2) это фактически неверно. Функциональные значения (в том числе тела именованных фукций и лямбда-выражения) представляет в CPython так называемый code object, который не является объектом в смысле ООП. Хотя у него есть контейнер в виде класса;

Прогоните как-нибудь текст первого моего примера через Cython (генерируемый им код совместим с CPython - по крайней мере, они сами так декларируют: "Cython provides [...] full runtime compatibility with all still-in-use and future versions of CPython") и посмотрите, что там внутри. Разумеется, там нет никакой кодогенерации на лету с кучей констант. Зато там есть примерно такое:

struct __pyx_obj_4test___pyx_scope_struct__bar {
  PyObject_HEAD
  PyObject *__pyx_v_i;
};

Экземпляр этой структуры создается и заполняется при создании и возврате лямбды, а print(i) вызывается из кода лямбды примерно таким образом:

__pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_print, __pyx_cur_scope->__pyx_v_i);

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

Естественно, транслятор с питона на C++ вынужден использовать средства C++.

Как говорил в подобной ситуации Джон Сильвер, “а ты что, ожидал встретить здесь епископа?” Что вы хотели увидеть-то там?

Попробуйте объяснить работу этой программы (не уверен, что она может быть успешно оттранслирована цитоном):

def d():
  return 1

l = lambda : d()

print (l())

class d:
  f = 0

print (l())
print (l().f)

Что именно, по-вашему, здесь захватывается в полях лямбда-объекта и как?

И вопрос со звёздочкой: какую семантику имеют скобки в теле лямбды?

Что вы хотели увидеть-то там?

Обещанную вами кодогенерацию с кучей констант, вшитых прям в питоновый код же, с JIT'ом каким-нибудь. Вы же говорили, все так работает:

Список 0..9, а в чём подвох? Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:

lambda : print (0)

lambda: print (1)

...

lambda: print (10)

которые помещаем в элементы списка lamdas при помощи функции foo.

Попробуйте объяснить работу этой программы (не уверен, что она может быть успешно оттранслирована цитоном):

Конечно, может, и успешно им транслируется.

Что именно, по-вашему, здесь захватывается в полях лямбда-объекта и как?

Простите, а зачем тут вообще что-то захватывать? Здесь же все символы в global scope, захватывать здесь ничего не нужно. Все находится через __Pyx_GetModuleGlobalName() и сразу выполняется по месту. Посидите самостоятельно, поупражняйтесь, посмотрите, как все работает в реальности, в каких случаях нужен захват (и он делается), а в каких - нет.

Обещанную вами кодогенерацию с кучей констант, вшитых прям в питоновый код же, с JIT'ом каким-нибудь.

В коде на C++, ага?

Вы же говорили, все так работает:

В интерпретаторе так и работает.

Простите, а зачем тут вообще что-то захватывать? Здесь же все символы в global scope, захватывать здесь ничего не нужно. Все находится через __Pyx_GetModuleGlobalName() и сразу выполняется по месту. 

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

Попробуйте написать так:

def d():
  return 1

l = lambda : d()

print (l())

class d:
  f = 0

print (l())
print (l().f)

def g():
  d = 1
  print (l().f)

g()

или, наоборот, так:

def h():

  def d():
    return 1

  l = lambda : d()

  print (l())

  class d:
    f = 0

  print (l())
  print (l().f)

  return l

d = 0
l = h()
d = 1
print (l().f)

Что здесь куда захватывается и когда?

Конечно, может, и успешно им транслируется.

Не забудьте проверить, чтобы результат этой оттранслированной программы соответствовал результату интерпретатора.

В коде на C++, ага?

А иначе же "теории не соответствует", ага?

В интерпретаторе так и работает.

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

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

Если внутри лямбды используются только глобальные переменные, то заниматься сохранением контекста нет смысла - глобальные переменные всегда доступны, они не уничтожатся, если вручную не сделать им del. Здесь действительно достаточно только имен переменных, сами соответствующие им объекты сохранять в контекст не нужно. Локальные объекты внутри какой-то функции - дело другое (см. код ниже).

или, наоборот, так:

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

struct __pyx_obj_4test___pyx_scope_struct__h {
  PyObject_HEAD
  PyObject *__pyx_v_d;
};

Знакомо, да? Вот ее использование в коде лямбды:

__pyx_outer_scope = (struct __pyx_obj_4test___pyx_scope_struct__h *) __Pyx_CyFunction_GetClosure(__pyx_self);
__pyx_cur_scope = __pyx_outer_scope;
__pyx_t_1 = __Pyx_PyObject_CallNoArg(__pyx_cur_scope->__pyx_v_d);

Это поле в ходе выполнения h() переприсваивается несколько раз, последний раз это выглядит так:

  __pyx_t_1 = __Pyx_Py3ClassCreate(((PyObject*)&PyType_Type), __pyx_n_s_d, __pyx_empty_tuple, __pyx_t_2, NULL, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 10, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __Pyx_GOTREF(__pyx_cur_scope->__pyx_v_d);
  __Pyx_DECREF_SET(__pyx_cur_scope->__pyx_v_d, __pyx_t_1);
  __Pyx_GIVEREF(__pyx_t_1);
  __pyx_t_1 = 0;

Это поле в ходе выполнения h() переприсваивается несколько раз

Очень хорошо. А теперь напоминаю, с каким вашим высказыванием я спорю:

генерируется "лямбда-класс" с полями-ссылками на захваченные "внешние" объекты (ну, или в случае простейших типов - просто значениями этих объектов), и с методом, код которого генерируется один раз, и который просто работает с соответствующими полями "своего" экземпляра лямбда-класса (как выше написали - "лямбда-объекта")

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

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

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

Если внутри лямбды используются только глобальные переменные, то заниматься сохранением контекста нет смысла - глобальные переменные всегда доступны, они не уничтожатся, если вручную не сделать им del. Здесь действительно достаточно только имен переменных, сами соответствующие им объекты сохранять в контекст не нужно. Локальные объекты внутри какой-то функции - дело другое

Любой объект, на который есть актуальные ссылки (в том числе из лямбды) не уничтожается в силу принципа сборки мусора. Неважно, глобальная это переменная или нет. У нас есть ссылка на имя, а имя ссылается на значение.

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

Надеюсь, вы видите, что Cython не генерирует плюсовый код? Ах да, не видите, вы же рассчитываете, что другие вам все расскажут и покажут, а вы будете только задавать глупые вопросы с умным видом. Так вот, Cython генерирует код на C. Там нет классов. Лямбда - это обычная функция, которая принимает параметр __pyx_self, который является грубым аналогом плюсового this (который так же первым параметром неявно принимают в плюсах нестатические методы классов). Через него и можно добраться до контекста/замыкания, как я показал выше, контекст неразрывно связан с конкретным экземпляром "лямбда-объекта", на который собственно и указывает этот __pyx_self.

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

Как говорится, хоть стрижено, хоть брито - все голо.

Так вот, Cython генерирует код на C.

Да какая разница-то? Вы самомодификацию кода на C++ или на C ожидали увидеть?

Лямбда - это обычная функция,

Ну слава богу, наконец!

как я показал выше, контекст неразрывно связан с конкретным экземпляром "лямбда-объекта"

Как же он неразрывно связан, если, по вашим собственным наблюдениям, к лямбде никаких обращений нет, а контекст меняется?

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

Как же он неразрывно связан, если, по вашим собственным наблюдениям, к лямбде никаких обращений нет, а контекст меняется?

А какие должны быть "обращения к лямбде" в C коде, вызов функции что ли? А зачем? А вот обновление её контекста по ходу его изменения, разумеется, производится. Я не просто так акцентировал внимание на том, что генерируемый код - не плюсовый, и понятие "класс" там отсутствует. Производится просто эмуляция "лямбда-объекта" с использованием негустого набора доступных в C средств, вот и все. Забавно, что приходится разжевывать такие элементарные вещи.

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

Просто конкретно в C нет средств сделать контекст "частью лямбды" в вашем представлении - с помощью конструкций языка. Но тем не менее даже там у лямбда-объекта есть своя "память" в виде структурки с набором захваченных внешних по отношению к нему объектов. А как это выглядит со стороны питона - вам уже написали ниже, как и куда смотреть. Засим откланиваюсь, удачи в дальнейшем образовании, всего хорошего.

А какие должны быть "обращения к лямбде" в C коде

Не в C коде, а в питоновском. Контекст лямбды у вас меняется без упоминания в питоновском исходнике этой лямбды. Объект так не работает.

Я не просто так акцентировал внимание на том, что генерируемый код - не плюсовый, и понятие "класс" там отсутствует. Производится просто эмуляция "лямбда-объекта" с использованием негустого набора доступных в C средств, вот и все. Забавно, что приходится разжевывать такие элементарные вещи.

Я не знаю, зачем вы это разжёвываете, потому что это понятно. Но мы же говорим о питоновских классах, а не плюсовых или их сишной эмуляции.

У списка нет элементов, окститесь. Список - это массив с поинтерами на объекты. Можете это проверить, сохранив один и тот же объект в список (или даже в разные списки), и так же на итерации позвав id от «элемента»

Подсказка: замыкание где? В «контейнере», или в code object?

Замыкание, конечно, в code object, но это никак не относится к обсуждаемому вопросу.

А что касается id, то Вы правы.

Фишка не в List<Action>, а в том что создается функциональный объект, со своей памятью. Впрочем так же происходит и в случае питона - там также создается функциональный объект со своей памятью. В моем примере можете добавить вывод print(id(l)) чтобы в этом убедиться. Либо сделать dis и также убедиться что вызывается MAKE_FUNCTION.

Если под отладчиком посмотрите содержимое лямбды (l), то там можете увидеть __closure__, который содержит ссылки на свободные (захваченные) переменные (в нашем случае только одну ссылку idx_copy), а в __code__.co_freevars названия захваченных переменных. И если посмотреть на id переменных, которые лежат в __closure__, то можно заметить, что все они соответствуют последней копии idx_copy.

Я немного модифицировал пример - добавил две переменные в замыкание, чтобы было проще смотреть, что попадает в захваченные переменные.

def create():
    lambdas = []
    idx_copy = 0
    for idx in range(10):
        idx_copy = idx + 1
        print ('-', id(idx), id(idx_copy))
        lambdas.append(lambda : print(idx_copy, idx))

    return lambdas

import dis 
dis.dis(create)

def test():
    for l in create():
        l()
        print (id(l), [(c.cell_contents, id(c.cell_contents)) for c in l.__closure__], l.__code__.co_freevars)

test()

А вот так можно сделать небольшую ошибку и сломать свою лямбду:

def create():
    lambdas = []
    idx_copy = 0
    for idx in range(10):
        idx_copy = idx + 1
        print ('-', id(idx), id(idx_copy))
        lambdas.append(lambda : print(idx_copy, idx))

    del idx_copy

    return lambdas

Причем здесь указатели на функции я простите не понял. Мы все таки вели разговор про замыкания.

Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.

Там оба захватываются - полно онлайн компиляторов - можно и посмотреть.

Лексическое замыкание, конечно, существует в питоне. Но это не значит, что захватываются какие-либо значения в лямбда-объекте. Лексическое замыкание означает просто сохранение лексического контекста имён свободных переменных. Сохраняются имена, а не значения.

Вам тот же самый вопрос.

Что касается C#, то, насколько я понимаю, функциональный объект создаётся именно приведением лямбда-выражения к объектному типу Action. Хотя настаивать не буду, так как этот язык мне неизвестен.

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

  1. function.__closure__

  2. codeobject.co_freevars

  3. closure-variable

  4. Python Scope & the LEGB Rule: Resolving Names in Your Code – Real Python

Как вы сказали выше "Охотно верю, но надо просвещать людей." - просвещайтесь пожалуйста.

По поводу вашего вопроса вам уже ответили - там глобальный скоп - там нет свободных переменных и они не захватываются - __code__.co_names и function.__globals__ вам в помощь.

Что касается C#, то, насколько я понимаю, функциональный объект создаётся именно приведением лямбда-выражения к объектному типу Action.

Вы неправильно понимаете. Почитать можно тут Lambda expressions - Lambda expressions and anonymous functions - C# reference | Microsoft Learn , тут Built-in reference types - C# reference | Microsoft Learn и тут https://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx

Вы сами-то читаете тексты, ссылки на которые даёте?

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

Что касается C#.

Вы неправильно понимаете. Почитать можно тут Lambda expressions - Lambda expressions and anonymous functions - C# reference | Microsoft Learn

Ну и тут прямо написано:

You use a lambda expression to create an anonymous function.

...

Any lambda expression can be converted to a delegate type. The types of its parameters and return value define the delegate type to which a lambda expression can be converted. If a lambda expression doesn't return a value, it can be converted to one of the Action delegate types; otherwise, it can be converted to one of the Func delegate types.

То есть именно то, что я написал: лямбда-выражение – это функция; её можно преобразовать в значение (экземпляр) типа (класса) Action. Что и делает ваш List<Action>.

И по выделенной ссылке:

delegate is a reference type that can be used to encapsulate a named or an anonymous method.

То есть сама по себе лямбда объектом в смысле ООП не является, представляя собой просто исполняемый код, интерпретируемый в своём лексическом окружении; но она может быть инкапсулирована в объект класса Action или Func. Это в точности именно то, о чём я пишу всё это время, и с чем (как я понял) Вы и ваш товарищ спорите.

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

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

То есть именно то, что я написал: лямбда-выражение – это функция; её можно преобразовать в значение (экземпляр) типа (класса) Action. Что и делает ваш List<Action>.

лямбда-выражение не функция. Вы могли бы с этим ознакомиться, если бы читали более внимательно и прочитали бы, например, последнюю ссылку (довольно объемную, но для вас там есть оглавление).

но она может быть инкапсулирована в объект класса Action или Func. Это в точности именно то, о чём я пишу всё это время, и с чем (как я понял) Вы и ваш товарищ спорите.

Вы неумело жонглируете словами, с этим я, и мой коллега по опасному бизнесу, и спорим.

Ниже IL (intermediate language) кода на C# из примера выше. Как видно ни <>c__DisplayClass0_0 ни <>c__DisplayClass0_1 не расширяют Action, но являются функциональными объектами.

.class private auto ansi '<Module>'
{
} // end of class <Module>

.class public auto ansi beforefieldinit HelloWorld.Program
	extends [System.Runtime]System.Object
{
	// Nested Types
	.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0'
		extends [System.Runtime]System.Object
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Fields
		.field public int32 idx

		// Methods
		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x210c
			// Code size 8 (0x8)
			.maxstack 8

			IL_0000: ldarg.0
			IL_0001: call instance void [System.Runtime]System.Object::.ctor()
			IL_0006: nop
			IL_0007: ret
		} // end of method '<>c__DisplayClass0_0'::.ctor

	} // end of class <>c__DisplayClass0_0

	.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_1'
		extends [System.Runtime]System.Object
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Fields
		.field public int32 idx_copy
		.field public class HelloWorld.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals1'

		// Methods
		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x210c
			// Code size 8 (0x8)
			.maxstack 8

			IL_0000: ldarg.0
			IL_0001: call instance void [System.Runtime]System.Object::.ctor()
			IL_0006: nop
			IL_0007: ret
		} // end of method '<>c__DisplayClass0_1'::.ctor

		.method assembly hidebysig 
			instance void '<Main>b__0' () cil managed 
		{
			// Method begins at RVA 0x2118
			// Code size 69 (0x45)
			.maxstack 3
			.locals init (
				[0] valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler
			)

			IL_0000: ldloca.s 0
			IL_0002: ldc.i4.4
			IL_0003: ldc.i4.2
			IL_0004: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, int32)
			IL_0009: ldloca.s 0
			IL_000b: ldarg.0
			IL_000c: ldfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_0011: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_0016: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted<int32>(!!0)
			IL_001b: nop
			IL_001c: ldloca.s 0
			IL_001e: ldstr " -> "
			IL_0023: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string)
			IL_0028: nop
			IL_0029: ldloca.s 0
			IL_002b: ldarg.0
			IL_002c: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_1'::idx_copy
			IL_0031: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted<int32>(!!0)
			IL_0036: nop
			IL_0037: ldloca.s 0
			IL_0039: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear()
			IL_003e: call void [System.Console]System.Console::WriteLine(string)
			IL_0043: nop
			IL_0044: ret
		} // end of method '<>c__DisplayClass0_1'::'<Main>b__0'

	} // end of class <>c__DisplayClass0_1


	// Methods
	.method public hidebysig static 
		void Main (
			string[] args
		) cil managed 
	{
		// Method begins at RVA 0x2050
		// Code size 160 (0xa0)
		.maxstack 3
		.locals init (
			[0] class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>,
			[1] class HelloWorld.Program/'<>c__DisplayClass0_0',
			[2] class HelloWorld.Program/'<>c__DisplayClass0_1',
			[3] int32,
			[4] bool,
			[5] valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>,
			[6] class [System.Runtime]System.Action
		)

		IL_0000: nop
		IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::.ctor()
		IL_0006: stloc.0
		IL_0007: newobj instance void HelloWorld.Program/'<>c__DisplayClass0_0'::.ctor()
		IL_000c: stloc.1
		IL_000d: ldloc.1
		IL_000e: ldc.i4.0
		IL_000f: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
		IL_0014: br.s IL_0059
		// loop start (head: IL_0059)
			IL_0016: newobj instance void HelloWorld.Program/'<>c__DisplayClass0_1'::.ctor()
			IL_001b: stloc.2
			IL_001c: ldloc.2
			IL_001d: ldloc.1
			IL_001e: stfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_0023: nop
			IL_0024: ldloc.2
			IL_0025: ldloc.2
			IL_0026: ldfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_002b: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_0030: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_1'::idx_copy
			IL_0035: ldloc.0
			IL_0036: ldloc.2
			IL_0037: ldftn instance void HelloWorld.Program/'<>c__DisplayClass0_1'::'<Main>b__0'()
			IL_003d: newobj instance void [System.Runtime]System.Action::.ctor(object, native int)
			IL_0042: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::Add(!0)
			IL_0047: nop
			IL_0048: nop
			IL_0049: ldloc.1
			IL_004a: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_004f: stloc.3
			IL_0050: ldloc.1
			IL_0051: ldloc.3
			IL_0052: ldc.i4.1
			IL_0053: add
			IL_0054: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx

			IL_0059: ldloc.1
			IL_005a: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_005f: ldc.i4.s 10
			IL_0061: clt
			IL_0063: stloc.s 4
			IL_0065: ldloc.s 4
			IL_0067: brtrue.s IL_0016
		// end loop

		IL_0069: nop
		IL_006a: ldloc.0
		IL_006b: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::GetEnumerator()
		IL_0070: stloc.s 5
		.try
		{
			IL_0072: br.s IL_0085
			// loop start (head: IL_0085)
				IL_0074: ldloca.s 5
				IL_0076: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>::get_Current()
				IL_007b: stloc.s 6
				IL_007d: ldloc.s 6
				IL_007f: callvirt instance void [System.Runtime]System.Action::Invoke()
				IL_0084: nop

				IL_0085: ldloca.s 5
				IL_0087: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>::MoveNext()
				IL_008c: brtrue.s IL_0074
			// end loop

			IL_008e: leave.s IL_009f
		} // end .try
		finally
		{
			IL_0090: ldloca.s 5
			IL_0092: constrained. valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>
			IL_0098: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
			IL_009d: nop
			IL_009e: endfinally
		} // end handler

		IL_009f: ret
	} // end of method Program::Main

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x210c
		// Code size 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Object::.ctor()
		IL_0006: nop
		IL_0007: ret
	} // end of method Program::.ctor

} // end of class HelloWorld.Program

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

Ну вообще то там написано, что замыкания захватывают значения свободных переменных. 

Где именно такое написано??? Можно конкретную цитату?

Они сами свободные переменные захватывают, а не их значения.

лямбда-выражение не функция. Вы могли бы с этим ознакомиться, если бы читали более внимательно и прочитали бы, например, последнюю ссылку (довольно объемную, но для вас там есть оглавление).

Можно конкретную цитату? Строго говоря, конечно, функция – это именованное лямбда-выражение.

Ну и дальше, какое отношение ваша простыня на ассемблере ВМ имеет к семантике языка питон или С#? Естественно, что, когда вы дойдёте до машинного кода, там вообще останутся одни байты. Мы говорим о семантике конструкции языка, а вы зачем-то приводите реализацию. В реализации разницы вполне может не быть (как, например, между целым и булевским значением в машинном коде), но в семантике она есть.

То же и ваш коллега по опасному бизнесу. Сам своими руками в своей простыне второй футамуровской проекции нашёл реализацию контекста в питоне. Нашёл, где и как этот контекст изменяется при продвижении выполнения по тексту программы (лексической области видимости). Всё верно нашёл, целеустремлённо. И после этого сам пишет, что лексический контекст является полем лямбды, хотя сам же убедился, что он находится снаружи её и общий для всех операторов лексической области видимости, они его изменяют друг за дружкой.

Прикладные навыки без фундаментальных знаний. Горе от ума прямо.

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

Можно конкретную цитату? Строго говоря, конечно, функция – это именованное лямбда-выражение.

Сами уж поищите, вы ловко виляете - понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте.

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

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

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

Сами уж поищите, вы ловко виляете - понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте.

Я вас, может быть, удивлю, но любая функция в питоне захватывает контекст, а не только лямбда. Так как оператор def является просто-напросто присваиванием чуть по-другому отформатированной лямбды:

def a(i):
  def f():
    return i
  return f

i = 1
ff = a(3)
i = 2
print (ff())

Простыня на IL для того чтобы вы посмотрели, что контекст захвачен, а Action-ном там не пахнет

Я этого и не собирался отрицать.

Вы ошибаетесь. Ваш последний пример работает не так как бы вам хотелось:

а объекты и даже типы, соответствующие этим именам, вы можете менять в любой момент, переопределяя имена, и лямбда всё это съест.

Если у вас захвачена переменная из Enclosing scope (как у меня в примере idx_copy) то "честным" способом их значения нельзя поменять в месте вызова лямбды. Я вам предлагал убедиться заглянут в дебагер, я дал вам ссылки на описание co_freevars, а вы продолжаете одно и тоже повторять.

Уфф.

Вы ошибаетесь. Ваш последний пример работает не так как бы вам хотелось

В каком смысле не так как бы мне хотелось? Он возвращает значение 3 из лексического контекста замыкания функции f. Без всяких явных лямбда-выражений.

Вы ж выше написали, что “лямбда-выражение не функция” и “понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте”. Или что вы этим хотели сказать? Чем лямбда-выражение не функция?

Если у вас захвачена переменная из Enclosing scope (как у меня в примере idx_copy) то "честным" способом их значения нельзя поменять в месте вызова лямбды.

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

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

def a(i):
  def f():
    return i
  def s(n):
    nonlocal i
    i = n
  return f,s

i=1
ff,ss = a(555)
i=2
print(ff())
ss(777)
i=3
print(ff())

Обратите, кстати, внимание, что в этой программе два разных замыкания ff и ss используют один и тот же лексический контекст имени i.

Я не знаю зачем вы мне показываете мои же примеры, зачем вы вырываете мои слова из контекста и хотите, чтобы я сам себя же опровергал.

Это довольно скучно и мне реально надоело — оставайтесь при своем мнении.

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

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

  1. Что лямбда является объектом в смысле ООП, который сохраняет в своих полях значения свободных переменных (вариант – содержит внутри себя свой лексический контекст).

  2. Что функция отличается от именованной лямбды чем-то, кроме очевидного отличия в синтаксисе.

  3. Что замыкания захватывают значения свободных переменных.

  4. Что эти гипотетические захваченные замыканием значения нельзя изменить извне замыкания.

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

Не согласен.

  1. Не знаю, что вы подразумеваете под "в смысле ООП" - дайте ваше определение, чтобы потом не доказывать вам, что я имел в виду совсем не то, что вы услышали.

  2. Я говорил это в контексте C#, там различия есть. В python отличия в том, что lambda имеет больше ограничений. Но с точки зрения работы замыканий отличий нет (ранее я вам предлагал посмотреть на dis и заметить там вызов MAKE_FUNCTION, что как бы намекает).

  3. Замыкания захватывают объекты, содержащие значения свободных переменных. Я выше признал, что имел в виду это. И также указал, что я против вашего утверждения, что захватываются только имена.

  4. Я не утверждал, что им нельзя изменить значения извне. Я утверждал, что обычным способом эти значения в месте вызова замыкания изменить нельзя, как мы, например, можем сделать с объектом в глобальном скопе - и в этом случае они как раз захвачены только по именам. Свободным переменным можно изменить значения извне потому что python позволяет залезть грязными руками в __closure__ у функции. Да это документировано, но это работа с внутренним состоянием, что конечно не поощряется. Можете считать это оптимизацией со стороны python; если посмотрите у замыкания co_code, увидите обращение к свободным переменным по индексам, а не по именам.

Основной предмет спора был в том, что вы утверждали, что захватываются только имена. Также вы утверждали, что захватывается контекст. Я приводил примеры и "доказывал", что для enclosing scope это не так. Я утверждал, что захватываются только те объекты, которые в лямбде используются. При этом если enclosing scope содержит другие переменные, то доступа к ним в точке вызова замыкания мы никак не получим. Потому что у замыкания нет доступа к этому контексту, а только к свободным переменным и/или глобальным (захваченным по именам).

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

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

Я продолжаю это утверждать, иначе как вы можете объяснить этот пример?

def a(i):
  def f():
    return i
  def s():
    nonlocal i
    i = s
  return f,s

ff,ss = a(555)
f=ff()
print(f,id(f))
ss()
f=ff()
print(f,id(f))

Какой объект в данном случае захватывается в качестве i замыканием f(), если захватывается объект, а не имя? Когда образовалось замыкание, имя i имело значение числа 555, а потом то же самое имя i получило значение функции s. Это явно два разных объекта.

Именно имя является элементом лексического контекста.

Также вы утверждали, что захватывается контекст. Я утверждал, что захватываются только те объекты, которые в лямбде используются.

Я не очень понял это утверждение. Вы можете привести в питоне пример какого-нибудь объекта, который не используется в функции/лямбде, но присутствует в её лексическом контексте?

Насколько я представляю себе питон, объект из enclosing scope появляется в области видимости (т.е. лексическом контексте) тела функции только по факту упоминания в тексте этой функции своего значения или использования в операторе nonlocal или global. Если имя из enclosing scope явно не упомянуто в функции, то оно не затягивается в её лексический контекст, и мы конечно не можем его использовать. Но это не имеет отношения к замыканиям, это просто правило видимости имён.

Поэтому, на мой взгляд, наши формулировки здесь эквивалентны. По крайней мере, я не могу построить различие.

Теперь по пунктам.

Не знаю, что вы подразумеваете под "в смысле ООП" - дайте ваше определение, чтобы потом не доказывать вам, что я имел в виду совсем не то, что вы услышали.

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

Свободным переменным можно изменить значения извне потому что python позволяет залезть грязными руками в __closure__ у функции. Да это документировано, но это работа с внутренним состоянием, что конечно не поощряется.

Я вам выше привёл совершенно корректный и теоретически правильный способ изменения свободной переменной извне захватившего её замыкания. Он не является хаком и не использует руками поле __closure__. Это штатное использование механизма замыканий. Поощрять такой стиль программирования или не поощрять – другой вопрос, но это штатный механизм языка.

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

Про C# не буду с вами спорить.

Я немного расширил ваш пример:

def a(i):
  def f():
    return i
  def s():
    nonlocal i
    i = s
  return f,s

ff,ss = a(555)
f=ff()
print(f,id(f))
print(ff.__closure__, ff.__code__.co_freevars, (id(ff.__closure__[0]), ff.__closure__[0].cell_contents))
print(ss.__closure__, ss.__code__.co_freevars, (id(ss.__closure__[0]), ss.__closure__[0].cell_contents), (id(ss.__closure__[1]), ss.__closure__[1].cell_contents))
ss()
f=ff()
print(f,id(f))
print(ff.__closure__, ff.__code__.co_freevars, (id(ff.__closure__[0]), ff.__closure__[0].cell_contents))
print(ss.__closure__, ss.__code__.co_freevars, (id(ss.__closure__[0]), ss.__closure__[0].cell_contents), (id(ss.__closure__[1]), ss.__closure__[1].cell_contents))
print ('end')

Вот его вывод:

555 2178338577904
(<cell at 0x000001FB2F172080: int object at 0x000001FB2F18DDF0>,) ('i',) (2178338463872, 555)
(<cell at 0x000001FB2F172080: int object at 0x000001FB2F18DDF0>, <cell at 0x000001FB2F172140: function object at 0x000001FB2F18BB00>) ('i', 's') (2178338463872, 555) (2178338464064, <function a.<locals>.s at 0x000001FB2F18BB00>)
<function a.<locals>.s at 0x000001FB2F18BB00> 2178338568960
(<cell at 0x000001FB2F172080: function object at 0x000001FB2F18BB00>,) ('i',) (2178338463872, <function a.<locals>.s at 0x000001FB2F18BB00>)
(<cell at 0x000001FB2F172080: function object at 0x000001FB2F18BB00>, <cell at 0x000001FB2F172140: function object at 0x000001FB2F18BB00>) ('i', 's') (2178338463872, <function a.<locals>.s at 0x000001FB2F18BB00>) (2178338464064, <function a.<locals>.s at 0x000001FB2F18BB00>)

Теперь попробую ответить на ваш вопрос:

Какой объект в данном случае захватывается в качестве i замыканием f(), если захватывается объект, а не имя? Когда образовалось замыкание, имя i имело значение числа 555, а потом то же самое имя i получило значение функции s. Это явно два разных объекта.

Как вы можете видеть в выводе замыкание ff содержит только одно захваченное значение/объект. Значение хранится в специальном прокси-объекте - Cell Objects. И таким образом мы храним в замыкании прокси-объект со значением.

Замыкание ss содержит уже две захваченных свободных переменных - i и s. И при выполнении замыкания ss значение в прокси-объекте (в котором хранится значение i) заменяется значением s. Это видно по тому, что id прокси-объектов не изменились, а значения поменялись.

Это также хорошо видно в ассемблере. Вот для ff:

          0 COPY_FREE_VARS           1
          2 RESUME                   0
          4 LOAD_DEREF               0
          6 RETURN_VALUE

Вот для ss:

          0 COPY_FREE_VARS           2
          2 RESUME                   0
          4 LOAD_DEREF               1
          6 STORE_DEREF              0
          8 LOAD_CONST               0
         10 RETURN_VALUE

Так что "Это явно два разных объекта." не является верным утверждением.

Я не очень понял это утверждение. Вы можете привести в питоне пример какого-нибудь объекта, который не используется в функции/лямбде, но присутствует в её лексическом контексте?

Насколько я представляю себе питон, объект из enclosing scope появляется в области видимости (т.е. лексическом контексте) тела функции только по факту упоминания в тексте этой функции своего значения или использования в операторе nonlocal или global. Если имя из enclosing scope явно не упомянуто в функции, то оно не затягивается в её лексический контекст, и мы конечно не можем его использовать. Но это не имеет отношения к замыканиям, это просто правило видимости имён.

Если мы понимаем лексический контекст лямбы так узко, то я с этим согласен.

Я вам выше привёл совершенно корректный и теоретически правильный способ изменения свободной переменной извне захватившего её замыкания.

Я не согласен. Это "жульничество". У обоих замыканий захватывается один и тот же "объект". Я говорил про случаи, когда вы находитесь за пределами области видимости, в которой объявлено замыкание. А так вы продемонстрировали функциональную реализацию инкапсуляции.

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

Я вам уже приводил пример:

def create():
    lambdas = []
    idx_copy = 0
    for idx in range(10):
        idx_copy = idx + 1
        print ('-', id(idx), id(idx_copy))
        lambdas.append(lambda : print(idx_copy, idx))

    del idx_copy

    return lambdas

Если мы продолжим работать с idx_copy после формирования замыкания (внутри функции create - надеюсь что вы это имели в виду), то мы либо будет менять значение, которое отразиться на замыкании, либо мы вообще можем сломать замыкание, если используемую переменную удалим.

Мы можем отдать замыкание из нашего контекста в другую лексическую область, а сами продолжать работать в своём лексическом контексте в параллельной нитке, например.

Вот это мне не понятно, приведите пример, чтобы у нас не было разногласий.

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

Конечно есть "питоновский" класс - Function Objects, который хранит ссылку на замыкание - cpython/funcobject.h. Замыкание является кортежом/туплом ячеек Cell Objects.

Как вы можете видеть в выводе замыкание ff содержит только одно захваченное значение/объект. Значение хранится в специальном прокси-объекте - Cell Objects. И таким образом мы храним в замыкании прокси-объект со значением.

Если под объектом вы понимаете этот прокси-объект, то я снимаю свои возражения. Как видите, он хранит как раз имена и служебную информацию о контексте.

Я не согласен. Это "жульничество". У обоих замыканий захватывается один и тот же "объект". Я говорил про случаи, когда вы находитесь за пределами области видимости, в которой объявлено замыкание. А так вы продемонстрировали функциональную реализацию инкапсуляции.

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

import threading, time

def a(i):

  class T(threading.Thread):
    def run(self):
      nonlocal i
      for j in range(100000000):
        i = i+1

  T().start()

  def f():
    return i

  return f

f = a(0)
print(f())
time.sleep(1)
print(f())

Является ли он жульническим?

Здесь класс T находится за пределами замыкания f(), но использует общий с ним лексический контекст для переменной i и поэтому легко её меняет. А применение замыкания вообще происходит в другом месте.

Конечно есть "питоновский" класс - Function Objects, который хранит ссылку на замыкание - cpython/funcobject.h. Замыкание является кортежом/туплом ячеек Cell Objects.

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

 Как видите, он хранит как раз имена и служебную информацию о контексте.

Скажите, пожалуйста, откуда вы так решили?

Здесь класс T находится за пределами замыкания f(), но использует общий с ним лексический контекст для переменной i и поэтому легко её меняет. А применение замыкания вообще происходит в другом месте.

Понятно, это аналогично тому, что мы уже разобрали. Тут ничего интересного нет.

Так этот класс через ячейки ссылается на самостоятельные объекты, представляющие имена и связанные с ними значения, а не содержит значения в себе самом.

Опять же, скажите, пожалуйста, откуда вы это взяли?

К примеру, если уничтожить замыкание, то участвующие в нём переменные останутся целы, если на них есть другие ссылки. Я говорил об этом.

Это вообще не относится к предмету разговора.

Как видите, он хранит как раз имена и служебную информацию о контексте. 

Скажите, пожалуйста, откуда вы так решили?

Так в вашей собственной распечатке двумя сообщениями выше это написано. Прямо имена распечатаны.

Понятно, это аналогично тому, что мы уже разобрали. Тут ничего интересного нет.

Неважно, интересно это или неинтересно, но это является контрпримером к вашему утверждению, что свободным переменным якобы нельзя изменить значения извне замыкания, не используя трюков с __closure__. Это самые обычные переменные, которые не находятся ни под каким арестом. Просто замыкание обладает доступом к их лексическому контексту, которым применяющий его код может напрямую не обладать. Но это чисто технический вопрос определения области видимости имён в каждой точке программы, который не является принципиальным запретом и не имеет прямого отношения к самому замыканию и его применению.

Так этот класс через ячейки ссылается на самостоятельные объекты, представляющие имена и связанные с ними значения, а не содержит значения в себе самом. 

Опять же, скажите, пожалуйста, откуда вы это взяли?

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

К примеру, если уничтожить замыкание, то участвующие в нём переменные останутся целы, если на них есть другие ссылки. Я говорил об этом. 

Это вообще не относится к предмету разговора.

Это как раз напрямую относится к предмету разговора, потому что является одним из следствий того, что эти переменные не являются собственными частями объекта, представляющего замыкание.

Так в вашей собственной распечатке двумя сообщениями выше это написано. Прямо имена распечатаны.

Вы невнимательно прочитали мой код, и не читали ссылки на Cell Object.

Неважно, интересно это или неинтересно, но это является контрпримером к вашему утверждению, что свободным переменным якобы нельзя изменить значения извне замыкания, не используя трюков с __closure__.

Еще раз - извне общего контекста это сделать нельзя. Ваш пример с потоком эквивалентен вашему примеру с ff и ss.

Так этот класс через ячейки ссылается на самостоятельные объекты, представляющие имена и связанные с ними значения, а не содержит значения в себе самом. 

Опять же, скажите, пожалуйста, откуда вы это взяли?

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

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

Это как раз напрямую относится к предмету разговора, потому что является одним из следствий того, что эти переменные не являются собственными частями объекта, представляющего замыкание.

Ну разберитесь уже.

Еще раз - извне общего контекста это сделать нельзя. Ваш пример с потоком эквивалентен вашему примеру с ff и ss.

Извне общего контекста, разумеется, этого сделать нельзя. Но вы-то писали, что этого нельзя сделать извне замыкания.

Я вам задал конкретный вопрос - откуда вы вот это взяли "Так этот класс через ячейки ссылается на самостоятельные объекты, представляющие имена и связанные с ними значения, а не содержит значения в себе самом."

Вот это вот – что такое?

(<cell at 0x000001FB2F172080: int object at 0x000001FB2F18DDF0>, <cell at 0x000001FB2F172140: function object at 0x000001FB2F18BB00>) ('i', 's') (2178338463872, 555) (2178338464064, <function a.<locals>.s at 0x000001FB2F18BB00>)

Не ссылка на имена i и s и их значения в их контексте?

Но как выяснили уже выше - вы ни мой код не можете нормально прочитать, ни ссылки не читаете.

Вы опять не выдерживаете предметную дискуссию по существу и начинаете хамить. Это некрасиво, даже если вы в чём-то были неправы.

Я говорю по существу очень простую вещь: что захвата переменных в лямбде по значению, как в C++, в питоне (и вообще в ФП) нет. А вы зачем-то с этим спорите.

Извне общего контекста, разумеется, этого сделать нельзя. Но вы-то писали, что этого нельзя сделать извне замыкания.

Я писал следующее:

Если у вас захвачена переменная из Enclosing scope (как у меня в примере idx_copy) то "честным" способом их значения нельзя поменять в месте вызова лямбды.

(отредактировано)

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

(отредактировано)

Вот это вот – что такое?

(<cell at 0x000001FB2F172080: int object at 0x000001FB2F18DDF0>, <cell at 0x000001FB2F172140: function object at 0x000001FB2F18BB00>) ('i', 's') (2178338463872, 555) (2178338464064, <function a.<locals>.s at 0x000001FB2F18BB00>)

Не ссылка на имена i и s и их значения в их контексте?

Если бы внимательно посмотрели код, то увидели бы, что имена печатаются не из замыкания, а из code object, и я вам показывал в ассемблере, что эти имена не используются при доступе к захваченным переменным. Посмотрите внимательнее.

Вы опять не выдерживаете предметную дискуссию по существу и начинаете хамить. Это некрасиво, даже если вы в чём-то были неправы.

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

Я говорю по существу очень простую вещь: что захвата переменных в лямбде по значению, как в C++, в питоне (и вообще в ФП) нет. А вы зачем-то с этим спорите.

Ну вот вы и опять передергиваете. Вы во-первых, это нигде не утверждали ранее, во-вторых, в самом начале дискуссии я написал следующее - "В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться.", в-третьих вы утверждали, что захватываются только имена, и я спорил с этим и аргументировано свою точку зрения обосновал. Тут вы опять не выдерживаете предмет дискуссии.

Я вам напомню, что вы писали:

4. Что эти гипотетические захваченные замыканием значения нельзя изменить извне замыкания.

Я не утверждал, что им нельзя изменить значения извне. Я утверждал, что обычным способом эти значения в месте вызова замыкания изменить нельзя, как мы, например, можем сделать с объектом в глобальном скопе - и в этом случае они как раз захвачены только по именам. Свободным переменным можно изменить значения извне потому что python позволяет залезть грязными руками в __closure__ у функции.

На мой взгляд, здесь написано, что значение захваченной замыканием переменной можно изменить извне замыкания по причине возможности грязного хака с __closure__.

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

Если бы внимательно посмотрели код, то увидели бы, что имена печатаются не из замыкания, а из code object

Так замыкание – это в реализации и есть code object плюс вспомогательная информация.

Давайте уж тогда определимся, что я пишу о (денотационной) семантике языка, и под “замыканием” имею в виду семантическую конструкцию языка Python, а не какую-то внутреннюю структуру действующего интерпретатора.

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

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

Ну вот вы и опять передергиваете. Вы во-первых, это нигде не утверждали ранее, во-вторых, в самом начале дискуссии я написал следующее - "В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться.", в-третьих вы утверждали, что захватываются только имена, и я спорил с этим и аргументировано свою точку зрения обосновал. Тут вы опять не выдерживаете предмет дискуссии.

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

Тем не менее, я продолжаю настаивать, что с точки зрения семантики языка захватываются только имена

Продолжайте.

Хорошо. Можете ничего не отвечать, но я вам и другим читателям для размышлений ещё такой код придумал:

def a():
  def f():
    return i
  def s():
   nonlocal i
   i = 1
  return f,s
  i = 0 
  
ff,ss = a()
##print(ff())
print("Что захвачено в этом замыкании?", ff)
ss()
print(ff())

Хороший пример, спасибо!

Вот такой на мой взгляд еще лучше:

class D:
  def __init__(self):
    self.x = 0

def a():
  def f():
    return i
  def s():
    nonlocal i
    i.x = 1
  return f,s
  i = D()
  
ff,ss = a()
ss()
print(ff())

Ваш код не работает, в отличие от моего. По понятной причине – объект класса D не создаётся. Что вы этим примером хотите проиллюстрировать и какой смысл у оператора i = D() в вашей программе? В моём коде оператор i = 0 имеет конкретное назначение, он помещает имя i в лексический контекст функции a при выполнении оператора def a, хоть сам и никогда не выполняется.

Это пример того, что можно написать по ошибке, случайно переставив операторы?

На мой взгляд i=0 и I=D() принципиально не отличаются. И интересно разобраться, почему поведение разное. Может у вас есть ответ? (Тот что вы написали выше меня не устраивает, потому что он ничего не объясняет).

Конечно, у меня есть ответ, потому что в данном случае оба кода мне понятны.

Мы имеем дело с двумя замыканиями ff и ss, каждое из которых в обоих случаях захватывает имя i из enclosing scope, то есть из лексического контекста функции a.

Это имя i появляется в лексическом контексте функции a благодаря тому, что в её теле есть оператор i=0 или i=D() (неважно, что именно в правой части). Лексический контекст строится в момент выполнения оператора def a, то есть при определении функции, поэтому фактически никакие операторы тела функции, включая и это присваивание, на данном этапе не выполняются. Просто строится список используемых имён (а именно, f, s и i). Поэтому и неважно, достижимо присваивание или нет. Фактически оператор присваивания в конце тела функции здесь выполняет такую же роль, которую в статическом языке выполнял бы оператор описания типа вроде int i. А если убрать это присваивание, то всё сломается – в функции s() будет ошибка в nonlocal, а в функции f() переменная i станет локальной.

Дальше замыкания, которые строятся уже при выполнении, а не при определении функции a(), захватывают имя i из лексического котекста функции a, но у этого имени нет значения, просто потому что никто его к этому моменту не присвоил. Почему я и говорю, что замыкание захватывает только имя.

Теперь, когда я выполняю своё замыкание ss(), то присваиваю переменной i из лексического контекста функции a значение 1. И дальше переменная i уже связана со значением, которое может быть использовано замыканием ff().

Вы же в своём коде начинаете с того, что в замыкании ss() пытаетесь обратиться к полю x переменной i. Но у вашей переменной i, так же как и у моей, к этому моменту ещё нет никакого значения, а соответственно нет и никаких полей, в том числе и поля x. Вот и получается ошибка NameError: cannot access free variable 'i' where it is not associated with a value in enclosing scope. То же самое произойдёт, если раскомментарить первый вызов ff() в моём коде.

На мой взгляд i=0 и I=D() принципиально не отличаются.

Да, но зачем писать сложное D(), если можно написать простой 0. Всё равно до правой части присваивания тут дело не доходит.

Ну ваш ответ не сильно отличается от предыдущих. Проблема (на мой взгляд) и в чем есть наши с вами противоречия, в том что вы постоянно смешиваете теоретический и практический уровни в своем ответе. Вы говорите "захватывается имя" - понятно что на теоретическом уровне так и происходит. Но на практике в текущих реализациях Python 3+ происходит немного не это.

В обоих случаях (i=0, i=D()) компилятор создает пустую ячейку (empty Cell Object), и ошибки происходят при попытке обратиться к объекту, который в ячейке должен лежать. Но не лежит. И название объекта (после компиляции) носит дополнительный характер, например, чтобы сообщить об ошибке понятным пользователю способом, но не для того чтобы получить значение переменной.

Т.к. мы получаем empty cell object - то этот вариант эквивалентен варианту с del idx_copy.

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

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

Deletion of a name removes the binding of that name from the local or global namespace, depending on whether the name occurs in a global statement in the same code block. If the name is unbound, a NameError exception will be raised.

Конечно, в реализации имя может быть представлено каким-то более хитрым внутренним объектом интерпретатора, например cell object, потому что реальная память компьютера адресная, а не ассоциативная, и самим по себе именем оперировать неэффективно. А также и потому, что имя значимо только в лексическом контексте.

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

сейчас C/C++ использую только для программирования микроконтроллеров и каких-то узких задач, большие программные комплексы никогда бы не стал на нём делать

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

А есть хорошая C++ библиотека, реализующая Communicating Sequential Processes типа горутин с каналами?

в С++ есть выбор, стеклес или стекфул корутины,

стеклес:
https://github.com/kelbon/kelcoro
стекфул:
userver (фреймворк) или boost context и тд

userver использует boost context, вроде.

Когда в последний раз вы получали настоящее удовольствие от программирования? 

Честно? Вот как после того как забросил программирование именно из-за С++ который вверг меня в настоящую депрессию и после огромного перерыва в 10 лет сел писать на Java, пя получаю удовльствие КАЖДУЮ МИНУТУ КАЖДУЮ КАРЛ
ОНО САМО ВСЕ ПИШЕТ, ВСЕ РАБОТАЕТ, ПАМЯТЬ ЧИСТИТ САМО, ЛОМБОК ВСЕ САМ ГЕНЕРИРУЕТ ЗА ТЕБЯ
я сажусь писать код с улыбкой, пишу и хохочу вслух как раз как клоун Роксо из вашего поста. Боже, писать не на С++ это такое наслаждение, не сравнимое не с чем в мире. Как говорится - дай человеку козу, чтобы он понял как хорошо жилось без козы, так же и у меня с С++

А я как автор, начинал с плюсов, ушёл в java/go/c# лет на 10 и по итогу вернулся к плюсам и неистово кайфую. В общем каждому своё.

А какой в итоге код получается в production на плюсах? Сколько ни смотрел на большие проекты, видел в основном более процедурный стиль (что-то близкое к Си с классами), ну там немного обмазано умными указателями и несколько стандартных контейнеров. На C# как раз можно писать используя его сахар и все остается понятным, но на плюсах как получается?

Наверное, произнесу страшную крамолу, но мне предпочтительней читать Си с классами, чем темплейтный хейл.

Мне со стороны кажется, что тогда уж странно, что нужен ломбок чтобы что-то там генерировать, а не на уровне языка, как в kotlin. Как минимум, все эти getValue() setValue(x) утомляют. Изобретение свойств в более поздних языках кажется действительно чем-то хорошим; по крайней мере для геттеров: для сеттеров ещё соглашусь, что написать setValue(...) будет нагляднее, что под капотом будет некая каша, но вот у геттеров обычно под капотом обычно чистенько и пустенько.

Генерация конструкторов — полезно и классно, но можно было и проще + для иерархий начинает работать довольно плохо. Но необходимость иметь пустой коструктор лишь чтобы отдать его на инициализацию значений через сеттеры в условный hibernate… ладно, это вина самого hibernate будет. Жаль, что record в язык добавлен довольно поздно…

Насчёт памяти: пожалуй, но если понадобится использовать closeable-поля будет немного неприятно вручную писать close для композирующего класса. К счастью, всё же это не часто нужно. В плюсах этой проблемы с деструкторами нет, да и в джаве, если finalize использовать, но тут он всё же не пахнет какой-то детерминированностью, да и вообще deprecated. Даже странно, что в lombok нет аннотации для такого (ну, кроме @Delegate, наверное… не уверен).

Ладно, немного извиняюсь, я тут написал о некоторых вещах, которые лично мне были неприятны, когда меня вынудили использовать джаву, но вне отношения ко мне они может и считаются нормальными. Но а так, всё же как человеку со стороны, мало знакомому с некой реальной работой на джаве и потому, возможно чего-то не знающему, хочется спросить: почему не один из "детей" джавы? Если нет желания терять кучу уже имеющихся библиотек и решений, чем плох какой-нибудь Kotlin? (хотя его немного трудно читать бывает, тут да, с этими гипервложенными угловыми скобками). Он вроде же как имеет всякое из lombok встроенное прямо в язык, null-safe, ну и синтаксическим сахаром подприправлен (хотя кому-то такое и минус).

А так, я люблю размещать всё в стеке👍 и как фанату стека мне всё же грустно от переиспользования объектов и оттого я всё же думаю, что модель памяти со сборщиком мусора заставляет слишком на себя положиться, что заставляет создавать объекты на 100500 довольно странных случаев, в котрых через стек было бы лучше. В этом плане C# со struct и Kotlin с value class были хоть как-то неплохи, но не скажу, что полноценно.

Ну, опять же, это куда больше моё раздражение, чем конструктив. Модель памяти C++ (пост-C++11) или раста мне всё же кажутся получше, если не требуется закидывать ссылки в кучу непонятных и неясных мест. Тогда да, наверное можно и замучиться.

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

Я не на столько давно в джаве, и абсолютно не осведомлен о тонкостях других языков, чтобы так детально ответить на озвученные вопросы. Но за все время что я изучаю и применяю программирование получается примерно так - любой язык из топ 10 имеет как свои плюсы(шутка про С ПЛЮС ПЛЮС) так и свои минусы, но при этом является самодостаточным и принесет работу и хорошую зарплату. Я джаву выбрал практически случайно, когда решил что пора вернутся в ИТ и мне с первой же минуты так понравилось на ней писать что я остальные языки даже не рассматривал. Тут ещё нужно иметь в виду небольшое искажение с моей стороны - на плюсах я писал в универе и после универа на очень душной работе в НИИ и было это в 2010 году, т.е мое представление о плюсах является устаревшим на 15 лет. Просто тогда и учеба и работа были ужасно душными и не понятными, все было супер сложно, кучу базовых функций приходилось руками реализовывать при этом следить чтобы все везде по памяти закрывалось и проч и проч
А сев за джаву я просто получил моментально язык который просто умеет всё что хочешь, на все есть готовые либы, все просто и понятно и дело пошло супер быстро. По поводу котлина думал уже в процессе учебы, что стоило выбрать его, однако выучил джаву и очень быстро нашел интересную работу и сейчас сижу и с удовольствием её работаю.
Может быть со временем я и прийду к С++, однако сейчас я абсолютно искренне радуюсь тому как легко и непринужденно работает джава

сейчас я абсолютно искренне радуюсь тому как легко и непринужденно работает джава

а теперь посмотрите на это:

  1. by using GC, applications spend 7–82% more wall-clock time and 6–92% more CPU cycles relative to their intrinsic costs.

  2. both Shenandoah and ZGC have worse (by factors of 10–100×) query latencies than the other three collectors!

отсюда https://www.linkedin.com/pulse/how-java-garbage-collection-works-sergey-derevyago-af84f

И что? Вы удивлены что GC не zero cost?

ну так разница заключается в том, что сэкономленное на GC время ты тратишь на то чтобы этот код написать. Куча продуктов работает на джаве, в том числе и хайлоад и никого не беспокоят эти мелочи, так как все давно уже на микросервисах. Там где производительность на столько решает - используют С++. Там где нужно быстро и просто писать безопасный код - берут джаву.
Суть заключается в том, что на том уровне на котором тебя будут беспокоить такие высокие материи а это сениор/техлид уже не имеет разницы какой язык использовать.
А я - всего лишь работник, я не решаю какой стек будет на проекте, я прихожу туда где нужен джава девелопер и девелоплю на джаве, получая удовольствие.

И да, то что она тратит время на работу ГК это как раз таки "легко и непринужденно" - от меня тут ничего не требуется, разве что выбрать какой ГК использовать, а дальше он сам работает и меня не беспокоит. Моя работа легка и непринужденна.

а теперь посмотрите на это:

А в С++, конечно, освобождение памяти не занимает ну ни капельки "wall-clock time" .

Я много лет писал на С++ и могу вам сказать, что он не быстрее ни на 92%, ни на 82%. А вот скорость реализации продукта на Java выше многократно. И если брать действительно скоростной код, который не занимается непрерывным выделением/освобождением памяти, то С++ не оказывается быстрее.

Я много лет писал на С++ и могу вам сказать, что он не быстрее ни на 92%, ни на 82%

если честно, то я тоже много лет писал на С++
и могу вам сказать, что mp_allocator дает ускорение в 5-8 раз, а не жалкие 100%
https://ders.by/cpp/norefs/norefs.html#4.2

и везде и всегда, GC - это УБИЙЦА производительности многопоточных приложений!

только бенчмарки сколько не делают, такого прироста не показывают. Сможете привести пример сравнения языков на реальных задачах, чтобы разница плюсов и джавы была в 5 раз хотя бы?
Недавно была кстати статья про то как 1 000 000 потоков сравнивали в разных языках, никаких х5 там не наблюдается

Сможете привести пример сравнения языков на реальных задачах, чтобы разница плюсов и джавы была в 5 раз хотя бы?

На самом деле можно но это будет супернизкоуровневая работа со специфичными фичами железа. Хотя джава и тут помаленьку догоняет)

В обычной работе этой разнице конечно неоткуда взяться

пример сравнения языков на реальных задачах

как и все остальные, вы сейчас не учитываете один маленький, но очень ключевой момент:

Ради этого места реального бизнеса АБСОЛЮТНО нет смысла стараться. Хуже дурака -- только дурак с инициативой!

Нет смысла стараться нигде, кроме узкого места (bottleneck). Но развальцуете бизнесу bottleneck -- он сразу пойдет веселее!

читайте https://ders.by/arch/scale/scale.html

как и все остальные, вы сейчас не учитываете один маленький, но очень ключевой момент:

И ради избегания этого боттлнека, который может возникнуть а может и не возникнуть, стоит использовать С++ в продуктовой разработке каких-нибудь крудов? А чтобы что?

GC жабы - позор.

Именно по этому в рейтинги TIOBE С++ и Java стоят на соседних позициях с минимальным разрывом уже который год.
Если я верно трактую вашу позицию, получатся что все остальное кроме ГК в джаве на столько хорошо, что нивелирует позор Гк полностью. Верно?

Верно для бизнеса. Разработка дешёвая и стабильная, а памяти и процессоров можно докупить.

ок, давайте посмотрим на Архитектуру.

например, в IP-телефонии нужно за 100ms принять решение: разрешаем звонок или нет? по какому тарифу?
вопрос: а на какое время Java-сервис зависает в мусорке?

и еще.

если падает процесс Сервиса - это проблема, но не беда.
а вот когда морозится на несколько секунд, а потом продолжает как ни в чем не бывало... это уже БЕДА!

Разработка дешёвая и стабильная

только на мелких проектах.

например, в IP-телефонии нужно за 100ms принять решение

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

только на мелких проектах.

ЯНДЕКС, UBER, AVITO, БАНКИ(тупо все, в каждом есть вакансии на джаву) - такие мелкие проекты, просто пыль под ногами C++

А можете свои умозрительные заключения подтвердить какими-то реальными фактами? Ну там примерами какими-нибудь из жизни реальных компаний. Ибо факты говорят об обратном - Java используется повсеместно и как раз в КРУПНЕЙШИХ проектах, с большими требованиями именно к стабильности, тупо в каждом бигтехе что-то крутится на джаве.
А, ещё всякие мелочи типа Kafka, например. Ну тоже чисто грязь под ногтями

А можете свои умозрительные заключения подтвердить

увы, я только вышел поболтать. и никакого отношения ни к Архитектуре, ни к IP-телефонии не имею))

Java используется повсеместно и как раз в КРУПНЕЙШИХ проектах

так спросите "в КРУПНЕЙШИХ проектах", как они тюнят GC. и что все равно происходит.

так спросите "в КРУПНЕЙШИХ проектах", как они тюнят GC. и что все равно происходит.

Ну так тюнят и да, что-то происходит
но как видите всех устраивает и никто массово переводить проекты на С++ не стал чтобы этого избежать
Вопрос - а на С++ сколько нужно будет всего тюнить чтобы поднять проект размером с кнопоиск?

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

Гц сильно совершенствовался, стоит ознакомиться с теорбазой.

чтобы разница плюсов и джавы была в 5 раз хотя бы?

Единицы процентов, не более. Ни о каких "в разы" речи не идёт. Сравниваю ессно, реализацию Graal-я а не стандартный хот-спот.

и могу вам сказать, что mp_allocator дает ускорение в 5-8 раз, а не жалкие 100%

Вы смеётесь что ли? В Java всё и всегда выделяется в сложные каскадные пулы памяти. Поэтому если и критиковать Java, то за расход памяти, но никак не за производительность.

Вы смеётесь что ли?

ДА!
я сейчас мерзко хихикаю, потирая потные ладошки)))

но никак не за производительность

вы просто не понимаете, с кем разговариваете
мне еще лет 20 назад "прогрессивная общественность" пела байки про "инкрементальный GC", не делающий stop the world... даже Страуструп топил за GC в C++, а потом "вдруг" одумался ;)

короче, в https://www.linkedin.com/pulse/how-java-garbage-collection-works-sergey-derevyago-af84f есть ссылки прямо по теме. постарайтесь понять ЧТО там написано Специалитами.

Жаба - это не только расход памяти. GC жабы - позор.

не томите, расскажите что конкретно мы должны там увидеть, что вызвало потирание вами потных ладошек)

п.с. прочитал, ссылка точно правильная? какая-то вводная статья, на 99% состоит из воды, в качестве примера влияния GC на многопоточность дается ссылка на сайт оракла с тюнингом Java 6 (sic!), в теле статьи тоже обсуждается CMS который давно выпилили, такое чувство что это инфа для самых маленьких из 2010 года

прочитал, ссылка точно правильная?

ну, давайте читать вместе:

  1. pdf называется "Distilling the Real Cost of Production Garbage Collectors"

  2. и начинается с "Despite the long history of garbage collection (GC) and its prevalence in modern programming languages, there is surprisingly little clarity about its true cost. Without understanding their cost, crucial tradeoffs made by garbage collectors (GCs) go unnoticed."

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

и могу вам сказать, что mp_allocator дает ускорение в 5-8 раз, а не жалкие 100%https://ders.by/cpp/norefs/norefs.html#4.2

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

оверхед от нормально настроенного гц это единицы процентов (ну ок, в случае ультралоулатенси может будет 10), лень разбираться что эти безумные цифры означают, но гарантирую что гц не замедляет программы в два раза ни при каких условиях (ну кроме может быть искусственных примеров которые специально составлены чтобы получить такой эффект)

оно само всё пишет ... сам генерирует за тебя
но ... ведь это и есть метапрограммирование, причем в C++ это не внешний инструмент как ломбок, а мощнее

но ... ведь это и есть метапрограммирование, причем в C++ это не внешний инструмент как ломбок, а мощнее

Угу, такой мощный, что spaceship пришлось вводить на уровне языка, а не библиотекой для метапрограммирования.

так я не спорю, что метапрограммирование есть и там и там, и может оно даже мощнее и вообще может все что угодно... Вопрос только в том на сколько удобен ломбок и на сколько удобно всем этим пользоваться в плюсах. Так то в Java тоже есть встроенный инструмент для метапрограммирования, Java Poet называется, у нас в проекте он например автоматически публичный api генерирует из внутреннего, вопрос в том что это супер мощный инструмент, можно много всего навертеть, только это означает что нужно сидеть и наверчевать. Ломбок же буквально одна зависимость, которая моментально срезает просто необходимость писать 40% кода. Не нужно не настраивать ничего не иззучать, там 10 анноатций и у них есть буквально по несколько дополнительных настроек, всё. Разбираться в том как оно работает 15 минут.

Расскажите, как в С++ с его мощным метапрограммированием получить:

  • Тип, имя второго поля класса

  • Количество публичных полей класса

  • Количество чистых виртуальных функций.

  • Циклы по этим полям\функциям. Для каждой сделать что-то - в императивном стиле

В императивном, а не в страшном подмножестве языка под названием SFINAE или шаблонном функциональном стиле

Как писать более сложные конструкции, абстрактные примеры:

  • Сгенерировать оператор == для класса, реализация:
    сравнить каждое поле с каждым полем other экземпляра класса

  • Сгенерировать реализацию сеттеров для класса, например с механизмом notifyOnChanged (как в Qt или каких-нибудь других реализациях с сигналами).
    Хороший существующий пример на C# - https://github.com/Fody/PropertyChanged

Ответ никак. Никакими стандартными средствами языка такие вещи не провернуть. Есть отдельные библиотеки по типу boost::pfr или MagicEnum, решающие разные задачи с ограниченным применением, и даже суммарно все взять все эти сторонние библиотеки - они не приблизятся по возможностям генерации кода с C# \ Java. Просто потому что C++ пока предоставляет очень скудный функционал по статической рефлексии и [почти] никакой динамической рефлексии

Много писал на C++, и он мне нравился. До тех пор, пока не появился C++11. А там чем дальше, тем хуже. Понял, что теперь нужно быть уже не программистом, а каким-то "адвокатом", знающим наизусть тысячи принятых законов, каждый из которых имеет десятки подпунктов, а каждый из подпунктов в свою очередь имеет по несколько толкований. Затем я попал в проект с Java, и в сочетании со всем известной IDE, ощущение было, словно пересел с деревянной телеги, которую сам тянешь вместо лошади, в достойного представителя немецкого автопрома.

О, добро пожаловать в клуб, у меня было похоже, но на C# и Visual Studio =)

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

В точности мои ощущения когда случился Java-переход после 30 лет на С++ . Из С++ мне в Java не хватает только множественного наследования. А ведь было время когда считал что плюсы это идеальный язык. На чистую сишечку и сейчас смотрю с уважением и знаю её.

вот, это буквально то что я имел в виду! Сижу и радуюсь

это перевод..

эх, а я думал это саркастический ответ на недавнюю статью про раст, которая начинается со слов

Раст хорош, все это знают

dr rockso: i do C++

Я очень люблю С++, я искренне считаю что учиться программировать (при условии вузика\наличия времени) - надо на С++. Но спустя 10+ лет проф. программирования, я как-то прод предпочитаю писать на том же шарпе, где всё из коробки и просто работает.

Со временем пришел к тому же, системный софт на C# писать как-то приятнее и быстрее. В свободное время стараюсь изучать новые стандарты C++, чтобы быть в курсе, но пока желания что-либо написать на нём (кроме чисто спортивного интереса) не было. Для низкоуровневых embedded вещей всё еще предпочитаю простой Си.

Странная статья. Оригинальная статья датирована октябрем 2024, если я правильно понял. Автор вылез из танка? Для кого эта новость про новейший стандарт C++11? Для тех кто в C++ не заглядывал последние 15 лет?

Видимо автор вернулся в C++ недавно, поэтому и офигел, когда увидел, что сделано в С++11. В общем-то я тоже не трогал С++ довольно долго (с 2008 года), и тоже немного прифигел года 4 назад, когда увидел сколько всего с тех пор изменилось. Сейчас тоже с удовольствием использую C++17, планирую перейти на C++20

Если вы пытаетесь сделать проект, конкурирующий с тем, на котором зарабатывает Диктатор — тогда будьте готовы к тому, что вас будут годами, так сказать, «травить собаками».

абсолютно в точку!

"Здесь вообще всё просто так, кроме денег" (ц)

Как начавшему с плюсов, отчасти соглашусь.

Оверъюз темплейтов может быть хорош для каких-то библиотек, но вытаскивать их из этих самых библиотек хорошей идеей абсолютно не кажется.

Системы сборки — даже если и использовать какой-то vcpkg, условный boost там будет отставать на минорную версию, и так и с другими библиотеками. Нехорошо, что из-за отсутствия некого центрального репозитория и системы сборки, создатели библиотек по итогу всё кидают на произвол условного гитхаба.

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

Был бы рад (когда уже наконец?) добавлению рефлексии: без неё грустно многие вещи зазря вручную писать (жаль, что пока без аттрибутов).

Отсутствие единой реализации стандарта — было бы легко на душе выбирать для себя что-то одно, но для библиотек это нездорово. Хотя приятно, что неопределённое поведение постепенно из стандарта стирается.

Каких-то фич или сахара немного не хватает.

Такое у меня ви́дение.

Если вы считаете, что ваш язык лучше других — дайте мне что-то около часа, и я найду в нём достаточно гадостей. В свете этих находок ваш язык будет выглядеть как жалкое подобие чего-то такого, что можно назвать «удобным»

Фраза заставляет задуматься ;-) Поэтому несмотря на

спорный характер тезиса

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

мне все-таки захотелось задать

не очень корректный вопрос...

по идее, его надо задавать автору оригинала

...прямо здесь. А именно, какие главные недостатки сообщество С++ видит в фортране, если рассматривать его применительно к счетным задачам? Речь о современном, естественно, языке, а не о его криптологической версии на основе вычисляемых GOTO из 70-х ;-). Вопрос

без подвоха:

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

мне действительно интересно послушать "свежий взгляд со стороны".

А именно, какие главные недостатки сообщество С++ видит в фортране, если рассматривать его применительно к счетным задачам

Я вижу только один минус - я его не знаю) Последний раз я на Фортране что-то писал лет 10 назад) Мне понравилось)

Про число измерений можно вспомнить: может, каким-нибудь теоретикам-струн предела в 15 не хватит.

Фортран я знаю так себе, но для числодробилок, по-моему, он вполне себе ОК. Интересно, а есть Фортран-фронтэнд к Clang?

Нужна простенькая страничка, на которой содержится список компиляторов и сведения о том, насколько они соответствуют стандарту. 

Но эта страничка существует :D

https://en.cppreference.com/w/cpp/compiler_support

Язык чрезвычайно сложен из-за его истории. В С++ имеется масса устаревших конструкций, через которые приходится продираться для того чтобы найти что-то ценное.

Чтобы решить эту проблему, существуют Core Guidelines, и они регулярно обновляются

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

В сущности это сборник советов "как идеологически правильно писать на С++". Впрочем, на каждый совет там приведены примеры и обоснования, а также случаи, когда в целом можно и не следовать этим правилам.

Вообще, для поддержки всего, что там написано, существует либа GSL

https://github.com/microsoft/GSL

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

Я люто ненавижу RAII. Я встречаюсь с множеством ситуаций, где RAII мешает решать настоящие задачи инициализации сущностей.

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

Суть RAII - это момент вызова деструктора. Объект должен подчищать за собой все ресурсы которыми он завладел при его окончании времени жизни. При этом вам вообще никто не мешает реализовывать паттерны вида строитель, или тупо сделать пустой конструктор и отложенный вызов метода Init(), хоть это концепутально может быть и не очень верно.

При этом количество раз, где RAII даже в изначальном смысле бывает полезен - сильно превышает количество мест где он бесполезен. Для того, чтобы оценить всю прелесть RAII я рекомендую один раз написать что-то на С++, а потом попробовать переписать это на чистый Си :)

Ещё, с RAII в многопоточке конструктор всегда выполняется в одном потоке, а всякие методы Init() могут только добавить проблем. По моим наблюдениям такие Init() необходимы, чтобы использовать shared_from_this

Лучшее, что я до сих пор обнаружил в этой сфере — это сборщик Meson, но и он полон последствий идиотских решений

Я поработал с Meson довольно много (два года использовал его в продакшене) и могу сказать, что лучший - по прежнему громоздкий CMake. К сожалению, документация Meson оставляет желать лучшего, плюс как только нужно сделать что-то за рамками простых сценариев, реализовано из ряда вон плохо. И он жутко медленный (питончик под капотом, что тут ещё добавить). Впрочем, синтаксис его приятный + нативная интеграция с Conan радует.

CMake можно сделать совсем чуточку проще, используя вот этот набор скриптов

https://github.com/StableCoder/cmake-scripts

Более 95% сообщений об ошибках компилятора С++ выглядят гораздо хуже, чем сообщения об ошибках всех остальных языков.

Тут полностью поддерживаю, но комитет по С++ делает шаги в этом направлении. static_assert и концепты решают часть проблем, по крайней мере в вашем коде) Используйте их, где только можно. Ошибки boost обречены быть ужасными, тут ничего не поделать)

Также рекомендую ChatGPT (и аналоги) для упрощения жизни себе) Эти инструменты реально хорошо расковыривают километровые логи ошибок.

Я искренне не понимаю как получать кайф от std::chrono. А сам я в последний раз получал кайф от программировани сегодня. Писал я на Rust, при этом не был поглощен заимствованиями, решал прикладную задачу и радовался тому как все хорошо получается :)

не понимаю как получать кайф от std::chrono

почитайте главу 5.7 Clocks and Timers

книги Nicolai M. Josuttis-The C++ Standard Library, 2nd Edition_ A Tutorial and Reference-Addison-Wesley (2012).pdf

За такие статьи надо бить, желательно ногами. Пишу как плюсовик. Для удовольствия пока что создан только го и шарп. Плюсы на текущем этапе доставляют слишком много боли

Насчет «есть абсолютно все», я бы поспорил. Например, не существует нормальной и удобной библиотеки для написания REST сервисов. В нашем проекте пришлось разрабатывать собственное решение. Есть, конечно, совершенно шизофренический «угловоскобочный» Boost Beast. Но даже название уже само говорит за себя, как в небезызвестном художественном произведении («Как вы яхту назовете так она и поплывет»). Есть еще cpphttplib, но эта библиотека хороша лишь для маленьких проектов.

А без HTTP/REST в современных реалиях никакое распределенное серверное приложение не сделать. В этой сфере однозначно «рулит» Golang.

И, кстати, Пайтон “sucks”!

Например, не существует нормальной и удобной библиотеки для написания REST сервисов.

RESTinio не пробовали? Или она не то, что вам нужно?

НЛО прилетело и опубликовало эту надпись здесь

C++ - это язык программирования, с которого можно получать удовальствия с 6 лет. Если у меня повится сын, первым язык, который он выучит, будут плюсы. Когда вырастит, еще добрым словом вспоминать будет!

Органам опеки стоило бы обратить на вас внимание

Когда вырастит

Что он вырастит? Геморрой от ночей дебага или шишку на лбу от бития об стол, когда дебаг совсем не удаётся?

Почему у вас такие ассоциации с таким прекрасным языком?) геморрой, битие об стол. Был печальный опыт, оставивший след в вашей душе?)

Лет 17-18 (или три четверти карьеры) печального опыта, оставившего такой след, да.

А в какой момент к вам пришло понимание, что плюсы отвратительный ЯП? Я сейчас мучаюсь с qml, мне хватило года, чтобы заныть, возненавидеть данную технологию, и начать учить что-то новое. Например kotlin compose - очень мощная технология, в которой у меня все задуманное получается.

Была тут одна статья на хабре, но это было давно и неправда.

Подспудное понимание, что плюсы — дерьмовый продакшен-язык, было со мной всегда. Просто я любил программирование само по себе и любил ребусы, а C++ подкидывает этих ребусов достаточно. Но одно дело — решать эти ребусы («мама, посмотри как я могу!») по фану по вечерам в личных проектах, где ты не несёшь ответственности, где тебя не разбудят в три ночи (или, в некоторых областях, не отдадут под суд) из-за проблем в твоём коде, и где ты можешь забить на поддержку старого кода/скучную задачу/понимабельность кода сокомандниками/етц, и другое дело — собсна, нести ответственность при продакшен-разработке со всеми вытекающими.

Понимание перестало быть подспудным и материализовалось большим и не терпящим игнорирования восклицательным знаком, когда я несколько месяцев не трогал плюсы, а вместо этого трогал другие пет-проекты на других языках и вообще другие разделы человеческого знания (потому что компанию, где я работал, купили, и отдел C++-программистов со мной вместе отправили на мороз, выплатив покрывающие мои потребности на несколько лет отступные, так что у меня были ресурсы расслабиться). Я как-то порефлексировал над изменениями настроения и всяким прочим и понял, насколько меня тошнит от C++ и насколько без него легче дышать: тошнит от невозможности писать код, которому я могу доверить управление чужой пенсией-инвестициями; от того, что ни я, ни окружающие меня люди, несмотря на топовые в области зарплаты и доскональнейший отсев, не знают плюсы и регулярно совершают ошибки и проблемы, связанные с самим языком и его кривейшим дизайном, а не с предметной областью; от очередных костылей и кривостей, привносимых новыми стандартами (сможете сказать сходу, для каких T валидна функция template<auto t> const auto& staticify() { return t; }, и почему?); от каких-то добавляемых в разных стандартах мелких деталей, которые всё равно делят язык на «до» и «после» (сможете сходу назвать отличия std::launder иstd::start_lifetime_as и вспомнить, в каком стандарте был std::start_lifetime_as, а в каком — std::start_lifetime_as_array?); от необходимости все эти стандарты держать в более-менее активной памяти, потому что собираемое старой версией легаси никуда не девается, и суммарная сложность языка растёт как снежный ком, а суммарная когнитивная сложность языка перегружает любой мозг, потому что дизайн языка совершенно неортогонален сам по себе, а временной аспект его эволюции («в этом стандарте это работает так-то, а в том — UB») добавляет ещё один совершенно когнитивно неортогональный аспект; от дизайна комитетом в худшем смысле, который и ведёт к этой неортогональности, и что не исправится вообще никогда…

Как-то так.

Пожалуйста, сначала выясните психотип ребенка.
Может он крутой хозяйственник, манипулятор (=продаван, стартапер) или лирик, а не харкор технарь.
Если навязывать несвойственное характеру - сломаете маленького человечка, не дав взлететь.
P.S. Я люблю C++, это первые пять лет моего программирования и любовь на всю жизнь.

Статья немного неоригинальная и можно спорить по многим пунктам. Но! Вот тут я просто не могу пройти мимо)

для PDP-11. Там нет нулевых адресов, а слова там имеют длину в 13,5 бит.

Два факта и оба - грубая и наглая ложь! Нулевой адрес есть и длина слова 16 бит (хотя шутку про 13.5 бит я оценил).

> Помню ожесточённые дискуссии с людьми, которые думали, что добавление пары чисел в шаблон было «быстрее», чем добавление тех же чисел прямо в код. И это — при том, что итоговый машинный код получался абсолютно одинаковым в обоих случаях.

После этих предложений читал текст по диаганали, очень странные аргументы приводятся, которые сами себя же и опровергают

о! еще одного выпустили из дурки

Я люто ненавижу RAII.

человек, который ненавидит RAII, должен писать на других языках, ибо это главная фича С++

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

умные указатели вообще никак не повлияли на частоту выделения памяти на стеке или в куче

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

Ну до полностью автоматического там как до Луны.

ну давайте смотреть:

  • абсолютное большинство аллокаций вообще закрывается аллокацией на стеке

  • абсолютное большинство аллокаций из кучи это простые аллокации через unique_ptr

  • если нужно настоящее множественное владение используется shared_ptr который решает эту задачу автоматически

  • и остаются совсем уж экзотические кейсы типа дерева с перекрестными ссылками, которые тоже можно закрыть в автоматическом режиме через использование weak_ptr

В последнем случае для получения автоматического управления нужно включать голову, но на практике это, дай бог, 0.1% от общего числа размещения объектов

Ну я лично не спорю, что RAII полезно на практике. Просто я за точность формулировок.

в абсолютном большинстве случаев C++ позволяет полностью автоматически управлять памятью

я не понимаю как это соотносится с оценкой "как до Луны"

Я думаю, что дело тут, в первую очередь, в том, что программисты просто забыли, с какой целью они пришли в программирование. Я знаю, что я учился кодить не для того, чтобы помочь кучке миллиардеров стать ещё богаче. И не для того, чтобы заставить иммутабельный движок рендеринга вывести кнопку василькового цвета. И я, определённо, пришёл в программирование не для того, чтобы выслужиться перед несколькими «диктаторами», правящими каким-нибудь опенсорсным проектом.

Я начал программировать из-за того, что это было здорово и интересно.

Задать вопрос автору не выйдет т.к. перевод, поэтому спрошу читателей: а что, варианты: "пойти в программирование, чтобы решать проблемы пользователей, уменьшать количество рутины, автоматизировать, придумывать новые крутые штуки и вообще, делать мир лучше" уже не рассматриваются?

Статья сильно, конечно, идеологизирована из-за беззаветной любви автора к плюсам. Я и сам, как и многие, начинал с C/C++, лет 10-12 так делал десктопные и консольные программы, побывал даже редактором почившего в бозе журнала Borland C++ Developers Journal... эх... (смахивая скупую мужскую слезу) Но потом незаметно пересел на Python, на котором пишу и до сих пор, и не так давно на Go. Ах да, в юности у меня был ещё Lua, но это совсем, так сказать, школьная любовь. Так вот. Холивары как были, так и остаются бессмысленными. Одному нравится это и это в языке Х, при этом не хватает того и того и лично бесит то-то. Другому другое... так было и будет всегда, какие бы инструменты не создавались хоть кем. Я кайфую, когда на любом языке решаю конкретную задачу, вижу, что моё детище дышит и живёт, при этом при созерцании результата я забываю все минусы, которые были при разработке. Это как родитель, радуясь ребёнку, прощает и забывает все тяготы родительской жизни.

Там нет нулевых адресов, а слова там имеют длину в 13,5 бит.

Что за бред? Если это шутка, то крайне неудачная.

Тааак, человек любит умные указатели, но ненавидет RAII, кто нибудь, сообщите ему что такое умный поинтер.

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

А про установку компилятора на cppreference - их вагон и маленькая тележка, нафига всё это добавлять? Сайт профильный, описывает стандарт плюсов, а компиляторы - дело личное программиста/компании, а не стандарта языка.

Это ведь старая статья, правда? В ней ничего про безумные попытки нагнуть стандарт, чтобы было легче писать LLVM?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий