У нас весь процессинг на FSM. Я не стану описывать, почему, но, подозреваю, что примерно во всем финтехе так.
Зачем тогда отвечать?
Рост числа состояний может приводить к линейному росту числа переходов, к факториальному, и ко всем между.
Если человеку нужно просто последовательно выполнить переходы между состояниями автомата, то он обычно не пользуется явными автоматами. А неявный автомат — это любой алгоритм, который выполняет процессор компьютера. Пусть современный высокопроизводительные процессор давно уже не автомат, а сложный набор автоматов, большая часть которых недоступна программисту — это не важно, я готов признать, что процессор — это автомат. Однако, уже на уровне ассемблера большая часть состояния и переходов этого автомата теряется, потому что состояние и переходы становятся неявными, они абстрагируется компилятором.
Во всех языках, где нет "break 2" или "break fooFor", выход из циклов делается через "goto afterFor". Не знаю, в какие ваши определения что и как вписывается, но пишут в коде "goto"
Что за «все языки»? Кобол? Фортран? Но даже в фортране уже был exit и cycle, которые аналогичны break и continue, а это 1957 год.
ОК, нужно редко, ограничено и всё такое. Тем не менее, задача есть, и с помощью написания текста "goto" в исходнике задача решается красиво.
Какая задача? Я хочу ее увидеть.
Любой код с goto можно переписать без goto введением флагов. Суть моего посыла в том, что код с goto в некоторых случаях выглядит лучше, а не то, что без goto прожить нельзя.
Я привел единственный пример, где с goto код выглядел бы лучше, и то можно поспорить, потому что разница небольшая. В данном случае имеет место более общая проблема, характерная для фортрана, Си, паскаля, и еще кучи старых языков — это отсутствие механизмов высвобождения локально выделяемых ресурсов. Давным-давно оперативная память была на вес золота, а запись на накопители была очень медленной, потому программист тщательно обдумывал ввод-вывод и создание больших временных объектов. Уже к 80-м годам у программистов возникла возможность оперировать достаточно большими структурами, и ручное выделение-высвобождение стало проклятием, потому что приводило к ошибкам.
Потому правильный вариант функции выше выглядит так:
_cleanup_fclose_ FILE *f1 = fopen("./file1", "r");
if (!f1) return;
_cleanup_fclose_ FILE *f2 = fopen("./file2", "r+");
if (!f2) return;
_cleanup_fclose_ FILE *f3 = fopen("./file3", "a");
if (!f3) return;
do(f1, f2, f3);
И никакое goto здесь рядом не валялось по лаконичности и чистоте кода.
Давайте, расскажите апологету шарпа — самого засахаренного и напичканного фичами мейнстримного языка, — какой он идеалист.
Что рассказать? Что люди пользуются неудобными технологиями просто потому, что они популярны? Так это мало для кого будет секретом. Вот MS Word — это отвратительный редактор текста, и, тем не менее, все им пользуются. А пользуются все потому, что все пользуются — вот такая вот простая истина. Люди ведь даже не ставят вопрос «должны ли мы использовать MS Word».
Можно осознанно махать кнутом на галере, можно целенаправленно разводить инвесторов стартапа, понимая, что все эти технологии — мусор, занавес, за которым можно грести бабло лопатой; а можно просто выдавать популярные идеи. Эта дихотомия отличается от противостояния с миром людей, которые придерживаются непопулярного мнения просто для того, чтобы придерживаться непопулярного мнения.
Вот то, что совершено точно повторяется и в моей вселенной, что тратится больше сил на обсуждение, чем требуется для начала получения результата
Нужно быстро заработать деньги — торгуй наркотой. Конкретно я пошел в программирование потому, что мне нравится писать программы. И мне не нравится, когда мне не нравится писать программы. Раньше программисты день писали программу, которая за секунду сделает вычисления, которые вручную делаются за два часа — сейчас тенденция идет обратная, и люди предпочтут помахать веслами пару часов, чем решить задачу красиво. Одно из следствий этой тенденции — активное накопление технического долга, когда веслами махать приходится месяц на фоне того, что первый программист всё так же тратит день, чтобы потом программа работала секунду.
Осталось несколько красивых и эффективных паттернов, которые так и остаются во власти goto: быстрый выход из нескольких циклов, освобождение ресурсов при отстутвии RAII, переход по состояниям автомата и некоторые другие
быстрый выход из нескольких циклов не делается при помощи goto. Нужно понимать, что условный переход и выход из цикла по структуре Дейкстры на уровне машинных кодов делается через те же переходы, но они фундаментально отличаются от перехода по goto структурированностью. Выход из нескольких блоков циклов-условий структурирован, потому что выходит из жестко заданного числа блоков. Ту же структуру можно было бы переписать на выходах одного уровня с дополнительными проверками флагов, но без этого код читается и пишется намного приятнее;
переход по состояниям автомата. Конечные автоматы нынче всерьез применяют разве что в промышленных контроллерах, где исполняемые устройства представляют собой естественные автоматы, вроде «кран открыт, кран полуоткрыт, кран закрыт» или «активен инструмент 1, инструмент 2, инструмент 3, инструмент не активен». У конечных автоматов есть большой недостаток — они плохо наращиваются, поскольку роста числа состояний приводит к экспоненциальному росту числа переходов. Для бесконечных автоматов goto идет лесом, потому что никто не будет делать программу с бесконечным числом goto — для этого применяются другие способы. Ну и таблицы функций никто не отменял, в конце-концов — это вполне себе структурные способы оформления конечного автомата;
освобождение ресурсов при отсутствии RAII. Я так понимаю, имеется в виду что-то вроде «goto cleanup» в функции после выделения ресурса. К сожалению, эта запись по сути является сахаром вокруг блочного оформления выделения-высвобождения.
FILE *f1 = fopen("./file1", "r");
if (f1) {
FILE f2 = fopen("./file2", "r+");
if (f2) {
f3 = fopen("./file3", "a");
if (f3)
do(f1, f2, f3);
fclose(f3);
}
fclose(f2);
}
fclose(f1);
И да, во многих современных языках эта проблема учтена. Но она не имеет отношения к goto.
До тех пор, пока остаются паттерны использования goto, которые не покрываются синтаксисом языков, консенсуса быть не может. Консенсус может быть, возможно, по "вермишельному" использованию goto
Консенсус отсутствует, в основном, у людей, для которых нет развлечений в жизни, кроме как всю жизнь доказывать другим людям, что черное — это белое, а белое — это черное, и что именно они — это те самые люди, которые возглавляет элитную группу Знающих. Упомянутый выше Таненбаум — это именно такой кадр, но есть огромное количество таких мудаков: ими забиты институты и коммитеты стандартизации. Комитеты сделали Algol 68 — Вирт сделал Pascal.
О, да. Роб Пайк, автор всемирно известных операционных систем Plan 9 и Inferno, а также языка программирования Limbo. Матёрее практика и не найти, пожалуй
Серьезно, что ли? Я думаю, здесь сидит немало людей, которые скажут, что Plan 9 была одной из лучших ОС-ей своего времени. Но победил Windows — наверное, это и есть самая лучшая в мире ось.
И да, Go является прямым наследником Limbo.
вытесняющую многозадачность в user-space без огромной потери производительности
Erlang в данном случае — это скомпилированный HiPE код.
Как мы видим, в среднем Erlang проигрывает на порядок, только на бинарных деревьях хорошие результаты показывает — судя по всему, там есть какие-то родные инструменты для этих структур.
Я могу сделать на Це fopen(«filename», «w») и пойти по своим делам, занимая важный ресурс, и не имеет значение, функция это, генератор или объект
В случае класса-итератора я могу выделить ресурс явно и передать его в генератор — это самый правильный подход к данному вопросу, вызывающий код полностью контролирует вызываемый. Я могу точно знать, когда цикл итератора начал работу и закончил работу, он не зависнет в промежуточном состоянии, завися от ресурса, который я решил закрыть, как может сделать генератор. Да, может оказаться, что требуемого для итерации ресурса нет, а итератор еще есть — какая разница, итерация не будет вызвана все равно. В генераторах же считается нормой управление ресурсом в самом генераторе. Особенно это актуально для сопрограмм на async/await.
Если копировать поведение итератора в генератор, то внезапно оказывается, что генератор и не нужен вовсе. Генератор нужен только тогда, когда структурность поведения нужно нарушать, задерживая ресурсы между итерациями, в противовес доступа к ресурсу только внутри одной итерации.
Если программист бесконтрольно открывает (на любом языке) файлы, сокеты, пайпы и не закрывает их, виноваты конечно же генераторы, а не то, что кто-то в этой цепочке дурачок
Существуют еще блокировки. отображения памяти, и прочие более легковесные вещи, который тоже умеют стерлять в ногу.
У некоторых инструментов есть очень узкое но эффективное применение, и когда его делают золотым молотком, инструмент превращается в антипаттерн
В многозадачном коде генераторы категорически неприемлимы. Они должны быть оформлены отдельной задачей, либо переработаны в итераторы — это основная моя претензия. Да, можно взвешивать за и против по поводу тестирования и оптимизации, но по параллелизации нечего взвешивать, потому что кол-во неявных ресурсов и проблем с ними еще больше возрастает, и при этом нет никакой гарантии, что итератор или вызвавший поток вернутся в то же состояние — это потенциально может превратить отладку программы с генераторами в ад.
Вообще, все ваши кейсы с генераторами касаются проблем чего угодно, только не генераторов, но ваше рвение впечатляет
Позиция защитников генераторов исходит из того, что «нож в печень, CPython вечен». Я же исхожу из некоего воображаемого транслятора, который умеет хорошо оптимизировать, параллелизировать, и давать гарантии по поводу надежности работы программы, и я вижу, что генераторы прямо-таки вставляют палки в колеса, заставляя сильно прогибаться транслятор и накладывая ограничения на выполнение, которые не дают просто говорить "да, пусть будет, оно никому не мешает" — потому что мешает.
И какие гарантии дает GDScript? Он такой же динамический как и Lua
Если посмотреть на тезисы моей статьи и на отличия GDScript, то можно увидеть, что они прошлись почти по всем ним, то есть:
убрали из языка генераторы и менеджеры контекстов;
убрали исключения. Этого тезиса не было в моей статье, но он был в комментариях к ней. Также, смотри обсуждение исключений в GDScript: https://github.com/godotengine/godot/issues/7643. Исключения рушат структуру кода, создают проблемы в параллелизации и оптимизации, которые очень важны в GDScript, в отличие от CPython;
переработали классы. Нельзя сказать, что теперь их нельзя изменить во время выполнения, потому что классы уже совсем не похожи на оригинальыне классы питона — это, скорее, модули, чем классы. Но да, их нельзя изменять во время выполнения, и множественного наследования нет;
сделали вывод типов и опциональную строгую типизацию в дополнение к динамике.
По ссылке сказано что его придумали только потому что задолбались писать байндинги внутренностей движка к языку общего назначения
Это общая проблема Lua, а не конкретно проблема Godot: в Lua для написания любых привязок нужно нагибаться раком.
Го появился чпустя 20 с лишним лет после эрланга, можно было бы обойтись без таких нелепых родовых травм
Вот примерно так 20 лет назад Таненбаум с Линусом спорил о том, что только лохи могут писать монолитную систему. Ну и где сейчас Таненбаум? Го написан матерыми практиками, он написан так, что работать, и работать быстро. Да, они могли бы начать пытаться вводить в ядро линукса какие-то правки для того, чтобы позволять переключать контекст в обработчиках сигналов без UB — они уже пытались ввести правки для кооперативного переключения контекстов при вызове функции между потоками, но так в ядро эти правки и не попали. Даже если бы у них получилось — они бы получили linux-only решение.
Реальность же такова, что ОС-и не дают никакой возможности сделать вытесняющую многозадачность в user-space без огромной потери производительности. Потому я и пишу, что поддержка многопроцессорности по прежнему медленно и неохотно развивается, находясь на достаточно низком уровне в среднем по палате, который ограничивается реализацией равенства процессоров и примитивными мелкогранулярными блокировками структур ядра, не способными эффективно разрешать значительные трения между процессорами. По этой причине в современных SMP системах ты ограничен независимыми задачами/потоками. Захотел взаимодействия потоков? Сам себе злобный буратина.
а питон изначально для чего?
а потом, как он развивался?
Питон был языком для написания системных скриптов. Его предок, ABC, был языком для обучения и для непрограммистов. Потом они развивались плохо.
Кажется, процесс развития питона устроен так, что любые идеи можно оформлять в PEP, выносить на суд общественности, и есть шансы, что их реализация будет в следующих версиях
Не кажется — это реальность. Есть конкретные идеи? Если придерживаться идей из моей статьи в полном объеме, то PyPy мне видится более приятным фундаментом, поскольку там уже много чего из стандартной библиотеке переписано на питоне (вместо Си).
Вы забыли вынести эту операцию в микросервис в Docker, управляемом Kubernetis, который запущен на кластере эмуляторов калькулятора Texas Instruments
Да, у меня есть знакомые, которые занимаются организацией хелло ворлдов в кластерах докера. Только не на Kubernetes, а на Docker Swarm. Конечно, я преувеличиваю. Но не намного.
Во-первых, здравый смысл. Когда я пишу программу, я стараюсь написать её как можно проще, а не как можно запутаннее. Если вы пишете программы по-другому — это не проблема языка.
Во-вторых, даже если считать это проблемой языка, это точно не является проблемой генераторов, поскольку такое может случиться с любым ресурсом
Если я явно выделил ресурс и явно высвободил его — нет проблем. Но это же не я писал про то, что вызывающий код не должен знать деталей реализации генератора, правильно? А генератор взял, и выделил ресурс после итерации (не путать с "в итерации"). А откуда я знаю, что он выделил? А я не знаю, мне же не нужно вычитывать каждую его строчку реализации, правильно? Вот я засунул указатель на итератор в долгоживущее поле, и пошел по своим делам. А генератор, тем временем, живет и держит какой-то важный или большой ресурс — но я-то думаю, что просто держу один фрейм генератора, и больше ничего.
Это не проблема, а возможность. Для того сопрограммы и придуманы.
Проблема заключается не в сопрограммах, а в том, что совместное выполнение алгоритмов резко повышает сложность их написания и понимания, как человеком, так и машиной.
В Питоне тоже так можно, надо только подходящую библиотеку найти. Или написать.
На двух ядрах вычисления делать можно? Сомневаюсь.
Вот только у значения внутри может быть ссылка. Часть ваших примеров из поста именно про это — и в Го тоже так можно накосячить.
Об этом и была вся моя статья: питон провоцирует плохой код, Go — хороший.
Если бы вы удосужились попробовать numba, вы бы с такой уверенностью не говорили про шансы у скорости выполнения
Я уже пробовал Cython, но вот беда: оно либо быстрое, либо питон. Два вместе не бывает — создатели GDScript поняли это. Да, на примитивных примерах достаточно несколько строчек типов написать в отдельном файле, и будет летать, но по мере роста сложности программы растет и сложность описания типов.
чтобы нужный интерфейс было легко найти, легко понять его эффекты и изменение логики было локализовано
Вот, именно для этого и нужен God object — чтобы нужную реализацию было легко найти и изменение логики было локализовано, а не размазоно тонким слоем по 20 файлам, как это обычно бывает в том же Java. Именно из-за отсутствия локализации кода реализации и возникла инструментальная работа с кодом и инструменты рефакторинга, которые в хороших языках не нужны или нужны очень-очень редко.
я сильно сомневаюсь, что вы пишете весь код в одном файле/классе
Конечно. Напомню определение: «Боже́ственный объе́кт (англ. God object) — антипаттерн объектно-ориентированного программирования, описывающий объект, который хранит в себе «слишком много» или делает «слишком много».»
Оно не обязывает иметь единственный объект — оно просто говорит про много/мало.
Поясню на примере. Вот монолит, стена текста, фу-фу костыли:
a = 1
b = 2
print(a+b)
А вот — грамотно спроектированная архитектура:
class Value:
def accept(self, v):
raise Exception('Not implemented')
class IntValue(Value):
def __init__(self, value):
self.value = int(value)
def accept(self, v):
return v.visit(self.value)
class Visitor:
def visit(self, value):
raise Exception('Not implemented')
class IntVisitor(Visitor):
def visit(self, value):
return int(value)
class UnaryOperation:
def op(self, value):
raise Exception('Not implemented')
class BinaryOperation:
def op(self, value1, value2):
raise Exception('Not implemented')
class AdditionOperation(BinaryOperation):
def op(self, value1, value2):
return value1 + value2
class PrintOperation(UnaryOperation):
def op(self, value1):
print(value1)
a = IntValue(1)
b = IntValue(2)
addition = AdditionOperation()
tmp = addition.op(a.accept(IntVisitor()), b.accept(IntVisitor()))
PrintOperation().op(tmp)
Мне уже не так смешно, как может быть вам, потому что я видел подобное воочию.
Ни о какой неделе на hello world речь никогда не идёт, и Enterprise Fizzbuzz — это пример очень плохой архитектуры.
Тем не менее, сейчас в проекте, за который мне дают еду, до 8 уровней вложенности папок в модуле на 60 тыс строк. И это JavaScript.
В то же время, никто не запрещает запускать избыточное число потоков в Go, чтобы гарантировать выполнение фоновых задач даже при тяжелых вычислениях. Там вон на C++/Java/JS и вовсе однопоток лепят сплошь и рядом — претензия к тому, что «программа зависает» на этом фоне мне кажутся преувеличенными.
Lua любят не за типизацию, а за простое и эффективное встраивание. Если бы питоновская виртуальная машина могла потягаться с Lua, то скорее всего везде бы пихали питон.
Питон по скорости выполнения не имеет шансов, но транслятор плюс базовая библиотека у него не такие уж и большие сами по себе. Проблема в том, что транслятор питона тяжело встаривать куда бы то ни было, потому что он изначально сделан как отдельная программа с кучей глобальных переменных и допущений об окружении, и трудоемкость встраивания этого такова, что проще создать свой язык заново. По этой причине до сих пор не получилось сделать множественные интерпретаторы для питона — даже такую задачу тяжело выполнить с нынешней реализацией CPython, куда там еще и встраивать.
Написав программу на хаскеле, я не могу ответить на вопрос «что делает эта программа?». У меня вызвалась func1, а потом func2, или же func2, а потом func1? Или и вовсе вызвалась только func1?
А нахера это знать?
Есть большое число вещей. которым важен порядок: файлы, сетевые протоколы, текстовый и графический интерфейс, общие структуры данных. То есть, практически всё, что связано с прикладными задачами, а не абстрактными моделями. В RTL хаскеля огромное кол-во императивного кода написано просто для того, чтобы компенсировать непредсказуемость программы на хаскеле, чтобы правильно инициализировать ресурс и высвободить его независимо от порядка, в который взбредет компилятору скомпилировать программу. И в итоге получается, что программист пишет программу не столько на хаскеле, сколько на RTL. Примерно как на Racket пишут, используя Scheme в качестве вспомогательного языка.
Для достаточно сложной программы это так на любом языке будет. Профайлеры человечество не просто так придумало, и впервые их начали применять вовсе не для Хаскеля.
Да, только в других языках профилированием можно достичь предсказуемой производительности кода.
Не могу разгадать смысл этого абзаца. Кажется, вы используете термины не в том значении, в котором их обычно используют.
Понятия диспетчеризации, полиморфизма, и приведения типов близки друг к другу. Если полиморфизм из времени компиляции переходит во время интерпретации, то он становится диспетчеризацией или приведением типа. Я не вижу смысла искать четкой границы между ними — ее тяжело найти, да и нет особой разницы в конечном итоге — это детали конкретной реализации. В общем случае это называется «полиморфизм». Я просто хотел написать, что приведение типов — это тоже частный случай полиморфизма.
Зачем тогда отвечать?
Если человеку нужно просто последовательно выполнить переходы между состояниями автомата, то он обычно не пользуется явными автоматами. А неявный автомат — это любой алгоритм, который выполняет процессор компьютера. Пусть современный высокопроизводительные процессор давно уже не автомат, а сложный набор автоматов, большая часть которых недоступна программисту — это не важно, я готов признать, что процессор — это автомат. Однако, уже на уровне ассемблера большая часть состояния и переходов этого автомата теряется, потому что состояние и переходы становятся неявными, они абстрагируется компилятором.
Я вообще не вижу здесь измерения производительности — они только меряют максимальное число подключений за условно бесконечный период времени.
Да. И в GDScript есть вывод типов. Да только вот беда: ни там, ни там классы не имеют почти ничего общего с классами CPython.
Вообще не помню, чтобы я писал что-то подобное когда-то. Особенно учитывая то, что в Си нет понятия классов.
Какая задача? Я хочу ее увидеть.
Я привел единственный пример, где с goto код выглядел бы лучше, и то можно поспорить, потому что разница небольшая. В данном случае имеет место более общая проблема, характерная для фортрана, Си, паскаля, и еще кучи старых языков — это отсутствие механизмов высвобождения локально выделяемых ресурсов. Давным-давно оперативная память была на вес золота, а запись на накопители была очень медленной, потому программист тщательно обдумывал ввод-вывод и создание больших временных объектов. Уже к 80-м годам у программистов возникла возможность оперировать достаточно большими структурами, и ручное выделение-высвобождение стало проклятием, потому что приводило к ошибкам.
Потому правильный вариант функции выше выглядит так:
И никакое goto здесь рядом не валялось по лаконичности и чистоте кода.
Что рассказать? Что люди пользуются неудобными технологиями просто потому, что они популярны? Так это мало для кого будет секретом. Вот MS Word — это отвратительный редактор текста, и, тем не менее, все им пользуются. А пользуются все потому, что все пользуются — вот такая вот простая истина. Люди ведь даже не ставят вопрос «должны ли мы использовать MS Word».
Можно осознанно махать кнутом на галере, можно целенаправленно разводить инвесторов стартапа, понимая, что все эти технологии — мусор, занавес, за которым можно грести бабло лопатой; а можно просто выдавать популярные идеи. Эта дихотомия отличается от противостояния с миром людей, которые придерживаются непопулярного мнения просто для того, чтобы придерживаться непопулярного мнения.
Нужно быстро заработать деньги — торгуй наркотой. Конкретно я пошел в программирование потому, что мне нравится писать программы. И мне не нравится, когда мне не нравится писать программы. Раньше программисты день писали программу, которая за секунду сделает вычисления, которые вручную делаются за два часа — сейчас тенденция идет обратная, и люди предпочтут помахать веслами пару часов, чем решить задачу красиво. Одно из следствий этой тенденции — активное накопление технического долга, когда веслами махать приходится месяц на фоне того, что первый программист всё так же тратит день, чтобы потом программа работала секунду.
Пример:
Это сахар вокруг:
И да, во многих современных языках эта проблема учтена. Но она не имеет отношения к goto.
Консенсус отсутствует, в основном, у людей, для которых нет развлечений в жизни, кроме как всю жизнь доказывать другим людям, что черное — это белое, а белое — это черное, и что именно они — это те самые люди, которые возглавляет элитную группу Знающих. Упомянутый выше Таненбаум — это именно такой кадр, но есть огромное количество таких мудаков: ими забиты институты и коммитеты стандартизации. Комитеты сделали Algol 68 — Вирт сделал Pascal.
Серьезно, что ли? Я думаю, здесь сидит немало людей, которые скажут, что Plan 9 была одной из лучших ОС-ей своего времени. Но победил Windows — наверное, это и есть самая лучшая в мире ось.
И да, Go является прямым наследником Limbo.
Есть что возразить? Или мне нужно показывать бенчи?
https://stressgrid.com/blog/benchmarking_go_vs_node_vs_elixir/ — в среднем Erlang в 4 раза больше потребляет ресурсов процессора, чем Go;
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/erlang.html — так и быть, соберу сам таблицу по Erlang vs Go:
Go: 25 s, 361 Mb;
Erlang: 8 s, 464 Mb;
Go: 4 s, 3 Mb;
Erlang: 21 s, 33 Mb;
Go: 2 s, 8 Mb;
Erlang: 16 s, 29 Mb;
Go: 7 s, 340 Mb;
Erlang: 56 s, 2000 Mb;
Go: 18 s, 1.5 Mb;
Erlang: 85 s, 24 Mb;
Go: 21 s, 1.6 Mb;
Erlang: 194 s, 24 Mb;
Go: 4 s, 1800 Mb;
Erlang: 42 s, 2700 Mb;
Go: 12 s, 160 Mb;
Erlang: 140 s, 750 Mb;
Go: 5.5 s, 31 Mb;
Erlang: 110 s, 52 Mb;
Go: 2 s, 3.5 Mb;
Erlang: 50 s, 24 Mb.
Erlang в данном случае — это скомпилированный HiPE код.
Как мы видим, в среднем Erlang проигрывает на порядок, только на бинарных деревьях хорошие результаты показывает — судя по всему, там есть какие-то родные инструменты для этих структур.
В случае класса-итератора я могу выделить ресурс явно и передать его в генератор — это самый правильный подход к данному вопросу, вызывающий код полностью контролирует вызываемый. Я могу точно знать, когда цикл итератора начал работу и закончил работу, он не зависнет в промежуточном состоянии, завися от ресурса, который я решил закрыть, как может сделать генератор. Да, может оказаться, что требуемого для итерации ресурса нет, а итератор еще есть — какая разница, итерация не будет вызвана все равно. В генераторах же считается нормой управление ресурсом в самом генераторе. Особенно это актуально для сопрограмм на async/await.
Если копировать поведение итератора в генератор, то внезапно оказывается, что генератор и не нужен вовсе. Генератор нужен только тогда, когда структурность поведения нужно нарушать, задерживая ресурсы между итерациями, в противовес доступа к ресурсу только внутри одной итерации.
Существуют еще блокировки. отображения памяти, и прочие более легковесные вещи, который тоже умеют стерлять в ногу.
В многозадачном коде генераторы категорически неприемлимы. Они должны быть оформлены отдельной задачей, либо переработаны в итераторы — это основная моя претензия. Да, можно взвешивать за и против по поводу тестирования и оптимизации, но по параллелизации нечего взвешивать, потому что кол-во неявных ресурсов и проблем с ними еще больше возрастает, и при этом нет никакой гарантии, что итератор или вызвавший поток вернутся в то же состояние — это потенциально может превратить отладку программы с генераторами в ад.
Позиция защитников генераторов исходит из того, что «нож в печень, CPython вечен». Я же исхожу из некоего воображаемого транслятора, который умеет хорошо оптимизировать, параллелизировать, и давать гарантии по поводу надежности работы программы, и я вижу, что генераторы прямо-таки вставляют палки в колеса, заставляя сильно прогибаться транслятор и накладывая ограничения на выполнение, которые не дают просто говорить "да, пусть будет, оно никому не мешает" — потому что мешает.
Если посмотреть на тезисы моей статьи и на отличия GDScript, то можно увидеть, что они прошлись почти по всем ним, то есть:
Это общая проблема Lua, а не конкретно проблема Godot: в Lua для написания любых привязок нужно нагибаться раком.
Вот примерно так 20 лет назад Таненбаум с Линусом спорил о том, что только лохи могут писать монолитную систему. Ну и где сейчас Таненбаум? Го написан матерыми практиками, он написан так, что работать, и работать быстро. Да, они могли бы начать пытаться вводить в ядро линукса какие-то правки для того, чтобы позволять переключать контекст в обработчиках сигналов без UB — они уже пытались ввести правки для кооперативного переключения контекстов при вызове функции между потоками, но так в ядро эти правки и не попали. Даже если бы у них получилось — они бы получили linux-only решение.
Реальность же такова, что ОС-и не дают никакой возможности сделать вытесняющую многозадачность в user-space без огромной потери производительности. Потому я и пишу, что поддержка многопроцессорности по прежнему медленно и неохотно развивается, находясь на достаточно низком уровне в среднем по палате, который ограничивается реализацией равенства процессоров и примитивными мелкогранулярными блокировками структур ядра, не способными эффективно разрешать значительные трения между процессорами. По этой причине в современных SMP системах ты ограничен независимыми задачами/потоками. Захотел взаимодействия потоков? Сам себе злобный буратина.
Питон был языком для написания системных скриптов. Его предок, ABC, был языком для обучения и для непрограммистов. Потом они развивались плохо.
Не кажется — это реальность. Есть конкретные идеи? Если придерживаться идей из моей статьи в полном объеме, то PyPy мне видится более приятным фундаментом, поскольку там уже много чего из стандартной библиотеке переписано на питоне (вместо Си).
Да, у меня есть знакомые, которые занимаются организацией хелло ворлдов в кластерах докера. Только не на Kubernetes, а на Docker Swarm. Конечно, я преувеличиваю. Но не намного.
Если я явно выделил ресурс и явно высвободил его — нет проблем. Но это же не я писал про то, что вызывающий код не должен знать деталей реализации генератора, правильно? А генератор взял, и выделил ресурс после итерации (не путать с "в итерации"). А откуда я знаю, что он выделил? А я не знаю, мне же не нужно вычитывать каждую его строчку реализации, правильно? Вот я засунул указатель на итератор в долгоживущее поле, и пошел по своим делам. А генератор, тем временем, живет и держит какой-то важный или большой ресурс — но я-то думаю, что просто держу один фрейм генератора, и больше ничего.
Проблема заключается не в сопрограммах, а в том, что совместное выполнение алгоритмов резко повышает сложность их написания и понимания, как человеком, так и машиной.
На двух ядрах вычисления делать можно? Сомневаюсь.
Об этом и была вся моя статья: питон провоцирует плохой код, Go — хороший.
Я уже пробовал Cython, но вот беда: оно либо быстрое, либо питон. Два вместе не бывает — создатели GDScript поняли это. Да, на примитивных примерах достаточно несколько строчек типов написать в отдельном файле, и будет летать, но по мере роста сложности программы растет и сложность описания типов.
Проблемы решены? Это фактически структуры Cи, просто синтаксис их определения подражает питоновым классам. Питоновые класы он не поддерживает.
Вот, именно для этого и нужен God object — чтобы нужную реализацию было легко найти и изменение логики было локализовано, а не размазоно тонким слоем по 20 файлам, как это обычно бывает в том же Java. Именно из-за отсутствия локализации кода реализации и возникла инструментальная работа с кодом и инструменты рефакторинга, которые в хороших языках не нужны или нужны очень-очень редко.
Конечно. Напомню определение: «Боже́ственный объе́кт (англ. God object) — антипаттерн объектно-ориентированного программирования, описывающий объект, который хранит в себе «слишком много» или делает «слишком много».»
Оно не обязывает иметь единственный объект — оно просто говорит про много/мало.
Поясню на примере. Вот монолит, стена текста, фу-фу костыли:
А вот — грамотно спроектированная архитектура:
Мне уже не так смешно, как может быть вам, потому что я видел подобное воочию.
Тем не менее, сейчас в проекте, за который мне дают еду, до 8 уровней вложенности папок в модуле на 60 тыс строк. И это JavaScript.
Вообще, над этим работают в Go:
https://codereview.appspot.com/10264044
https://github.com/golang/go/issues/10958
Однако, нужно понимать, что для высокопроизводительного си-подобного кода не так просто сделать подобное не потеряв производительности.
В то же время, никто не запрещает запускать избыточное число потоков в Go, чтобы гарантировать выполнение фоновых задач даже при тяжелых вычислениях. Там вон на C++/Java/JS и вовсе однопоток лепят сплошь и рядом — претензия к тому, что «программа зависает» на этом фоне мне кажутся преувеличенными.
Питон по скорости выполнения не имеет шансов, но транслятор плюс базовая библиотека у него не такие уж и большие сами по себе. Проблема в том, что транслятор питона тяжело встаривать куда бы то ни было, потому что он изначально сделан как отдельная программа с кучей глобальных переменных и допущений об окружении, и трудоемкость встраивания этого такова, что проще создать свой язык заново. По этой причине до сих пор не получилось сделать множественные интерпретаторы для питона — даже такую задачу тяжело выполнить с нынешней реализацией CPython, куда там еще и встраивать.
Есть большое число вещей. которым важен порядок: файлы, сетевые протоколы, текстовый и графический интерфейс, общие структуры данных. То есть, практически всё, что связано с прикладными задачами, а не абстрактными моделями. В RTL хаскеля огромное кол-во императивного кода написано просто для того, чтобы компенсировать непредсказуемость программы на хаскеле, чтобы правильно инициализировать ресурс и высвободить его независимо от порядка, в который взбредет компилятору скомпилировать программу. И в итоге получается, что программист пишет программу не столько на хаскеле, сколько на RTL. Примерно как на Racket пишут, используя Scheme в качестве вспомогательного языка.
Да, только в других языках профилированием можно достичь предсказуемой производительности кода.
Понятия диспетчеризации, полиморфизма, и приведения типов близки друг к другу. Если полиморфизм из времени компиляции переходит во время интерпретации, то он становится диспетчеризацией или приведением типа. Я не вижу смысла искать четкой границы между ними — ее тяжело найти, да и нет особой разницы в конечном итоге — это детали конкретной реализации. В общем случае это называется «полиморфизм». Я просто хотел написать, что приведение типов — это тоже частный случай полиморфизма.