All streams
Search
Write a publication
Pull to refresh
44
0

Программист

Send message
Можно внутри bar сделать if по типам, да. Вполне законно и это будет такая же диспетчеризация. Но есть одно но. Придется bar дописывать под каждый новый тип

Если "if" будет дописывать и выполнять компилятор во время компиляции, то проблем для писателя софта не возникнет.

Ого! Да минует меня участь читать ваш код после этого или работать с вами в одной команде в проектах где свыше 10-20 килострок кода.

А вот теперь представьте себе, что в JavaScript переопределение стандартного Array считается нормой. Не то, чтоб я был согласен с этой ситуацией — просто для справки, чтобы было ясно, что потребность есть, и ее довольно много.

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

Многопоточную программу нельзя представить как алгоритм иерархической структуры. Именно это я и называю «нарушает структурированность алгоритма».
https://ru.wikipedia.org/wiki/Структурное_программирование
По этой причине так медленно многопоточные приложения входят в индустрию — их по прежнему крайне сложно писать.


Да, типы, методы и функции из расширений тоже являются built-in, и нет, определенно не нужно менять смысл выражений (...), [...] и {...} в Python

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

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

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

Для иммутабельного tuple выражение x += (1,) эквивалентно x = x + (1,). Присваивается новое значение. Так работают все иммутабельные типы в Python. Мутабельные меняются "по месту".

Да? Так а почему x[0] += 1 не создает новое значение? Не буду мучать вопросами: потому что стандартная реализация операции STORE_SUBSCR не может видеть ничего дальше своего носа: у нее есть аргумент, она может работать только с ним. Даже несмотря на то, что изменение указателя "x" нам доступно, и потому мы можем неограничено менять значение. В том числе, это следствие бедности синтаксиса питона, не разделяющего изменяемые и неизменяемые данные, значение и ссылку на значение.

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

Корпоративной? Я так понимаю, речь идет про кооперативную. Го полностью поддерживает потоки на уровне ОС, но не отображает го-программы в потоки ОС 1 к 1. Причина лежит в слабой поддержке многопроцессорного железа в современных ОС, и в ближайшее время ситуация не планирует меняться. Например, я хочу из одной го-программы вызвать вторую, чтобы вторая го-программа обработала параметр и выдала результат, вернув управление первой го-программе. В случае нынешней реализации Go я не переключая контекста ОС сразу передаю управление другой го-программе, тратя на это порядка 200 циклов процессора. Если же у меня есть многопроцессорная система и моя программа не привязана к конкретному процессору, то на потоках обычно такая передача управления приводит к добавлению второй го-программы в планировщик другого ядра и запуском потока, с последующей остановкой потока первой го-программы и исключением ее из очереди планировщика первого ядра. Второй сценарий занимает примерно 5000-10000 циклов процессора. Вызвал функцию, называется.


По этой причине в pthreads есть/была поддержка кооперативной многозадачности, в windows fibers есть до сих пор, потому в Haskell и Go для сопрограмм используются именно кооперативность, которая может быть натянута на потоки ОС. В конце-концов, по этой причине возник nginx, который компенсирует неспособность ОС эффективно работать с большим числом легковесных задач.

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

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


Норм. Именно так и должно работать иерархическое владение ресурсами.

Иерархическое? Откуда такая гарантия? Что мешает передать ресурс (объект-генератор) мимо иерархии? Почему я и пишу про необходимость досконального изучения каждой строчки вызываемых фукнций.


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

Разные функции — разные алгоритмы. Это очевидно. Проблема генераторов заключается в том, что они заставляют разные алгоритмы выполняться совместно — это значительно повышает сложность понимания программы, не давая значимых преимуществ. Да, есть программы, которые изначально выполняют в ядре ОС или во внешних потоках несколько операций параллельно, и управляют ими в event-driven режиме — алгоритм уже изначально сложный, там разница будет лишь в форме записи кода. Но если такой острой необходимости в асинхронности нет, то и генераторы не нужны.


Реализация возвращенного вам итератора ("объекта-генератора") точно так же скрыта от программиста, не вижу разницы.

Там идет вполне целенаправленное движение в сторону того, чтобы кишки торчали в обе стороны: send, close, gi_frame, gi_yieldfrom. Да, это вполне себе взаимодействие подчинения, когда породивший генератор код владеет фреймом генератора и может создавать в генераторе исключения. Но независимые алгоритмы? Нет.


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

Канал тоже.



Только в частном случае. Например, «c := make(chan int, 1)» уже работает по-другому, заполняя канал без ожидания потребителя.


а когда оно будет запрошено — оно станет опять тем самым общим состоянием

И в Го это происходит точно так же.



В отличие от питона, у Go есть явное разделение на значение и ссылку. Канал передает значение, которое существует в один момент времени только с одной стороны канала.

Зачем в начале упоминается GDScript? Он с питоном не связан никак кроме похожего синтаксиса и не является языком общего назначения

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


Обычно игрострой выбирает Lua, но мой опыт написания логики для игр на Lua говорит, что без типизации контролировать корректность программы тяжело. Это одна из причин, почему так долго мучали Stalker и не могли выпустить его. По этой причине порядочный создатель движка должен придумать свой скриптовый язык, как Bethesda сделала свой Papyrus, как Blizzard свою модификацию Lua со строгой типизацией. Точно так же создатели Godot посмотрели на имеющиеся open source языки, и обнаружили, что не существует достаточно простого языка с приемлимой производительностью выполнения, который, в отличие от Lua, давал бы какие-то гарантии по поводу стабильности работы скрипта.
https://docs.godotengine.org/ru/latest/getting_started/scripting/gdscript/gdscript_basics.html#history

А вы посмотрите. Не особо-то он и узкоспециализированный. Там многие вещи, на которые вы сетуете, решены.

Язык Numba всего-лишь не поддерживает классы питона. Почти питон, ага.

Про numba не слышали?

Не слышал. Я узкоспециализированными оптимизаторами как-то не интересовался.

А с чего вы взяли, что вы в своем коде "вышли" из функции-генератора?

Вот именно! Откуда я знаю, где я вышел из генератора? Я не знаю. Пока я досконально не вычитал каждую строчку кода каждой вызываемой функции — я не могу быть уверен, что смогу предсказать, вышел ли я из контекста в генераторе или нет.


Нет, разница в том, что во втором примере итератор выходит из области видимости и удаляется перед print('Конец')

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


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

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


А чем "объект-генератор" принципиально отличается от канала? Почему объект-генератор у вас стал общим состоянием, а канал общим состоянием не является?

Типа «чем MS DOS отличается от линукса? То ОС, и то ОС». На уровне языка канал не является общим состоянием, потому у потоков нет общих данных в рамках канала. Может быть, в самой реализации канала и используются какие-то общие данные, но это скрыто от программиста. Объект-генератор, например, не может выдать следующее значение до того, как это значение будет запрошено, а когда оно будет запрошено — оно станет опять тем самым общим состоянием. За исключением неизменяемых примитивных типов, конечно же — потому хелло ворлды так сладко писать на генераторах.

Он изначально спроектирован как открытая замена проприетарной миранде.

Да. Но Миранда была спроектирована именно как абстрактная фантазия. А значит, хаскель тоже стал абстрактной фантазией.


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

Написав программу на хаскеле, я не могу ответить на вопрос «что делает эта программа?». У меня вызвалась func1, а потом func2, или же func2, а потом func1? Или и вовсе вызвалась только func1? Даже если допустить, что формально побочных эффектов у них нет — есть такое побочный эффект, как время выполнения и потребляемая память, которые не всегда получается вынести за скобки. Из-за этой проблемы, например, минимальные правки кода дают разницу производительности выполнения на порядки. У человека, пишущего диссертацию, есть условно неограниченное время на пердолинг, но у программиста, пишущего прикладную программу, есть ограничения и по времени разработки, и по времени выполнения. Прикладному программисту не нужно иметь хорошо формально доказуемые свойства, потому что он не будет эти свойства доказывать. Для него лучшее доказательство — это фактическая работа программы и удовлетворенность пользователя.


Какое отношение диспетчеризация имеет к монадам?

Во время компиляции же ж. По канону это именуется полиморфизмом: компилятор по типу (заранее известному) возвращаемого контейнера выбирает конкретную монадическую функцию. Это именно ad-hoc вариация, которая близка по духу к диспетчеризации, даже если конкретная функция сама полиморфна.


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

Монады — это сложный инструмент для простых действий. Подтверждение этому — число статей в интернете «что такое монада?», в которых лично я без продвинутого знания хаскеля ничерта не смог понять. И зачем нужна такая сложная конструкция? Чтобы написать «выполни мне одну команду вслед за другой».


Которого у вас в программах 99%, конечно же.

Весь GUI, внезапно, состоит из побочных эффектов. Почему на хаскеле его и не пишут.


А вообще, не спорю, удобно, когда монадок и контроля за эффектами нет: можно невозбранно внедрять бекдоры в функцию сравнения строк

Это разговор про неуловимого Джо: программа не может нанести вред, если она ничего не делает и ей никто не пользуется.

Всяко лучше, чем «ой, а у меня в STM-транзакции printf случайно, и компилятор вырезал нахрен весь мой код как UB, что делать-то».

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


Я вот [x for x in xs] читать не могу, у меня мозг ломается на четвёртом токене.

Не, это очень простой пример. Намного прикольнее читать вот такой код — здесь даже подсветка синтаксиса не поможет:
https://spapas.github.io/2016/04/27/python-nested-list-comprehensions/


strings = [ ['foo', 'bar'], ['baz', 'taz'], ['w', 'koko'] ]
[ (letter, idx) for idx, lst in enumerate(strings) for word in lst if len(word)>2 for letter in word]

И да, list comprehension — это рак. List comprehension в питоне ввели не потому, что он так хорош — дело в том, что альтернативные записи ещё хуже.
Why List Comprehension is Bad

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

Когда на хаскеле приходится писать императивную программу, то хошь-не хошь, а приходится. Вот эти вот «IO STM Maybe a», unboxed клинопись, и прочее.

нужно не забыть поплнить запасы попкорна к моменту, когда будут аналогичный «разбор» хаскеля

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


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

def foo(x):
    x.bar()
Как в данном случае сделать вызов аналогичный x.bar() без методов и их аналогов на функциях первого класса?

Например:


def foo(x):
    bar(x)

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


Как отвечать на сообщение «bar» знает только объект x.

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

Почему Python должен быть статически типизированным и статически компилируемым?

А почему нет? Статически он прекрасно компилируется, между прочим. Но на фоне сложных динамических структур данных выхлоп от этого получается смешной.


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

Типы из расширений питона — это тоже «встроенные типы», которые «не подлежат изменению»? А они таки не подлежат.


можно унаследоваться от встроенного типа и переопределить или дополнить его поведение, а из-за утиной типизации в общем случае нет нужды ожидать именно list или dict — достаточно объекта, который реализует набор методов.

Да, только код обрастает уродливыми конструкциями, вроде «a = MyDict({'first': 1, 'second': 2})». Никто ведь не заставляет весь сишный код интерпретатора изменять тип создаваемых объектов — я просто хочу поменять смысл конструкций «{...}» и «[...]» в моем собственном коде.


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

Плохая архитектура реализации не является какой-то отличительной чертой питона — много чего было плохо реализовано в других языках. У PHP не было классов вообще: ни динамических, ни статических — и, тем не менее, он был популярнее питона. Как так?


Там же, где try/except/finally, только под другим соусом.

Если говорить о том, что with является неявным блоком try...finally — да, согласен. Но у него есть основной, линейный алгоритм выполнения.


Вы же не нарекаете на то, что вытесняющая многозадачность средствами ОС нарушает структурированность исходного кода?

Многопоточный код нарушает структурированность алгоритма, но это нарушение дает вкусные плюшки — возможность работы на нескольких процессорах. По этой причине однопоточные программы так долго существуют на наших устройствах. Сопрограммы async/await и генераторы нарушают структурированность алгоритма, но не дают сравнимых плюшек, а лишь дают альтернативную запись для алгоритмов с функциями-callback-ами.

Детали реализации мешают обозревать структуру.

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


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

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

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

Что тайп скрипт? Мой вопрос, почему вы решили, что ВСЕ мечтают.

Конечно же не все мечтают. Кто-то пишет проекты на 10-20 тысяч строк, где можно наизусть запомнить все алгоритмы и структуры данных. Мечтают все или почти все, кто пишет крупные проекты, и кто постоянно спотыкается об «как мне перестать искать ошибки после каждой правки и начать писать код», а потом пишут в бложиках рассказы про «моя жизнь до и после тайпскрипта».

Information

Rating
Does not participate
Location
Украина
Registered
Activity