LINUX.ORG.RU

Vim или Emacs? А LISP в 2021?

 , ,


1

3

https://www.youtube.com/watch?v=8Q9YjXgK38I&t=42s

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

А ведь Crashbandicoot была годной игрой…

Что выбрать? Vim или Emacs?
Изучать в 2021 году Lisp? Если изучать, какой? Практика?
А не засмеют сотрудики?

Времени в сутках маловато, на всё не хватает.


Ответ на: комментарий от monk

Там ещё fixed, setprecision, … И всё это относится к конкретному объекту, а не к системе в целом. Синтаксически полный эквивалент

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

call_func(a, b)

буду делать

params.arg1 = a;
params.arg2 = b;
call_func(&params);

byko3y ★★★ ()
Ответ на: комментарий от byko3y

У меня есть теория, что Земля круглая. Это как бы очевидная вещь, а проблема не в этой — проблема в том, что средний окрепший мозг в вакууме, который осилил лисп и решил «вкусить всю мощь», напишет лютейший говнокод, который потом кроме него никто не сможет прочитать… и даже он сам спустя пару лет, потому нынче он уже пишет код в другом, более кошерном стиле.

Как-будто ньюб не напишет говнокод на простейшем Си?

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

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

И ещё важнее тупых языков и ещё более тупых практик его использования - регрессионное покрытие.

И потому, наверное, половина индустрии теперь бэкэнды и просто сервисы пишет и переписывает на Go?

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

mv ★★★★★ ()
Ответ на: комментарий от byko3y

Что это за продакшон такой лютый? Там вон пишут на питоне-руби, амазон и али на жаве, фейсбук компилирует свой пых — и им норм, а у тебя, видите ли, Go ресурсы жрет.

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

mv ★★★★★ ()
Ответ на: комментарий от monk

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

Ты сделал

try:
  old = param
  param = a
  {здесь код}
finally:
  param = old

а {здесь код} сделал что-то подобное, причем, ожидает, что после его вызова param останется неизменным и второй вызов {здесь код} прочитает это значение. А ты его взял и потер. Это и есть причина отказа от работы в глобальном контексте — вместо этого функции дергают только свои переменные, к которым никто кроме них не может получить доступа никаким случайным совпадением идентификаторов.

После fork() в каждом процессе независимые значения

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

переменные окружения для процесса
Read-only

man setenv

Если это делается не однократно перед выполнением основного алгоритма (или запуска другого процесса), то это говнокод. В частности, при выполнении setenv имеет смысл отключать все обработчики сигналов. Конечно, однопоточность сма собой подразумевается, поскольку вызов setenv сам по себе небезопасен в многопотоке.

Короче говоря, это всё одни и те же проблемы глобальных изменяемых состояний.

byko3y ★★★ ()
Ответ на: комментарий от monk

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

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

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

Расскажи это Торвальдсу.

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

Как ты это себе представляешь? Как в винде 95, где пользователю показываете окошко «вставьте дискету в дисковод» и на время этого окошка зависает вся программа? Потому что если она не зависает, то она продолжает что-то делать, состояние меняется, и ты не можешь просто взять и продолжить выполнение как ни в чем ни бывало.

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

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

byko3y ★★★ ()
Ответ на: комментарий от mv

Как-будто ньюб не напишет говнокод на простейшем Си?

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

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

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

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

А я когда-то писал, что Си — хороший язык?

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

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

Ну, про лисп ещё Пол Грэм писал, что это язык для «элиты» ;)

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

ГО будет мучаться со сборкой мусора, потому что у него того сборщика процентов 10 от оного у того же SBCL. Лет 5 назад смотрел, весьма удивился.

Кстати, контора другая была, но тоже проект-облако, и с Го всё те же самые проблемы: очень много кушает памяти, для классических демонов-сервисов не годится.

mv ★★★★★ ()
Ответ на: комментарий от Antonova

Vim, конечно. Странно, что в 2021г еще стоит такой вопрос.

А почему?

B0B ()
Ответ на: комментарий от mv

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

Lisp, C, C++ — это мой холл позора инструментов которые были или есть популярными, при этом чрезмерная перегруженность фичами убила язык. Хотя, конечно, стоило бы в этот холл вписать еще PL/I и Ada, но про них уже никто не помнит. Из новых претендентов на звание «оверинженернутое ненужно» есть Rust.

Ну, про лисп ещё Пол Грэм писал, что это язык для «элиты»

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

ГО будет мучаться со сборкой мусора, потому что у него того сборщика процентов 10 от оного у того же SBCL. Лет 5 назад смотрел, весьма удивился

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

причем, ожидает, что после его вызова param останется неизменным

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

Бывают, конечно, странные люди, которые считают, что после

$ a=1
$ sh -c "export a=2"
$ echo $a

должно вывестись 2. Но большинство читают документацию.

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

Такой же, как и выхлоп нескольких потоков. Не пойму в чём претензия. Ты не умеешь синхронизировать процессы?

monk ★★★★★ ()
Ответ на: комментарий от byko3y

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

Используй процессы, а не потоки. Тогда всё отлично работает. setenv всегда запускается перед fork для передачи параметров. Замечу, что самый безопасный почтовый сервер написан именно в таком стиле.

Короче говоря, это всё одни и те же проблемы глобальных изменяемых состояний.

Это проблемы лично твоего непонимания. Почитай-таки документацию.

monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от byko3y

Lisp, C, C++ — это мой холл позора инструментов которые были или есть популярными, при этом чрезмерная перегруженность фичами убила язык.

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

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

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

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

Сейчас, может быть, что-то поменяли, но 5 лет назад сборщик в Go был крайне примитивным. Годится, разве что, на тривиальные микросервисы: запустился отдельным процессом, пробежал стометровку, сдох.

mv ★★★★★ ()
Ответ на: комментарий от byko3y

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

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

Напоминаю классическое «Abort, Retry, Ignore?». И это было до того, как решили, что вместо предложения выбора надо бросать исключение.

Расскажи это Торвальдсу.

Он выбрал вообще отсутствие исключений вместо ограниченных из си++.

Как в винде 95, где пользователю показываете окошко «вставьте дискету в дисковод» и на время этого окошка зависает вся программа? Потому что если она не зависает, то она продолжает что-то делать, состояние меняется, и ты не можешь просто взять и продолжить выполнение как ни в чем ни бывало.

Ну да. И лучше выдать табличку «При записи файла закончилось место на диске» с одной кнопкой «ОК». Пользователь от такого «решения» счастлив.

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

??? Зачем разбираться? Текущее состояние ведь на месте. От последней записи есть количество байт из write(2).

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

Вот такие «программисты» писали системный интерфейс копирования в Windows. Когда на любую ошибку файл можно только качать заново.

monk ★★★★★ ()
Ответ на: комментарий от byko3y

Питон так-то чуть фичастее баша

То что надо для задачи «засунуть вывод из одной Си-библиотеки в другую».

какие-то абстракные обороты

Вполне конкретные. Просто ты не въезжаешь.

как-то конкретнее выражаться

Ну извини, мне лень всё на пальцах объяснять.

no-such-file ★★★★★ ()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от monk

С возобновляемыми исключениями.

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

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

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

alysnix ()
Ответ на: комментарий от mv

Реальная проблема была - цена лицензии LW

Ну хрен его знает, может в LW всё шоколадно. Они б ещё больше огораживались и вводили запретительный денежный ценз.

Кого в 2021 накладные вопросы колышут

Если дергать Си-функцию в 10 раз дороже чем нативную, то как бы нафига козе баян (ЛИСП)? Тогда можно и из питона дёргать.

в гетерогенном софте код на одном языке вызывает обширный код на другом

И в каком месте тут нужен ЛИСП? Это на bash делается, максимум на питоне.

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

Да, потому что это свойство открытой системы – из кубиков лего собрать звездолёт. А то о чём ты рассуждаешь это и есть прикручивание сбоку. Никакой LW и волшебные FFI тут вообще нафиг не упёрлись, можно просто завернуть внешнюю либу в какой-нибудь grpc и гонять как «микросервис». Вангую, что это ещё и быстрее и надёжнее будет работать.

no-such-file ★★★★★ ()
Ответ на: комментарий от monk

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

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

Такой же, как и выхлоп нескольких потоков. Не пойму в чём претензия. Ты не умеешь синхронизировать процессы?

Претензия в том, что stdou/in/err — это механизмы для однопоточного-однозадачного говна мамонта. Обрабатывают несколько потоков они отвратительно — напомни, какие там есть механизмы для того, чтобы при одновременной записи в stdout из двух процессов данные не выводились винегретом. Ты можешь вспомнить мутексы, а я могу вспомнить вопрос «зачем мне stdout тогда, если мне приходится еще и руками синхронизацию поверх него сооружать?».

byko3y ★★★ ()
Ответ на: комментарий от monk

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

Сколько там он десятков тысяч запросов в секунду обрабатывает?

byko3y ★★★ ()
Ответ на: комментарий от mv

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

А теперь возьми какую-нибудь macros-heavy либу, и потеряй всю свою простоту одним махом. Как ни странно, олдскульные сишники кодят на подмножестве Си — масштабы проблемы меньше, чем у крестов, но она есть. Например, я в своем коде категорические не использую присвоения в условиях while и if.

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

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

Сейчас, может быть, что-то поменяли, но 5 лет назад сборщик в Go был крайне примитивным

В 2015 (версия 1.5) сборщик мусора переписали.

byko3y ★★★ ()
Ответ на: комментарий от monk

С возобновляемыми исключениями программа может предложить пользователю выбрать как именно выбираться из ошибки.
Напоминаю классическое «Abort, Retry, Ignore?». И это было до того, как решили, что вместо предложения выбора надо бросать исключение

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

Расскажи это Торвальдсу.

Он выбрал вообще отсутствие исключений вместо ограниченных из си++

Вот. А мог бы показывать окошко «Abort, Retry, Ignore?». На сервере без монитора самое оно.

И лучше выдать табличку «При записи файла закончилось место на диске» с одной кнопкой «ОК». Пользователь от такого «решения» счастлив

Что тебе не нравится? Если приложение умеет возобновлять прогресс, то она возобновить прогресс. Это никсовые консольные поделки при ошибке отваливаются с концами — но над убогими грешно смеяться.

Зачем разбираться? Текущее состояние ведь на месте. От последней записи есть количество байт из write(2)

Всё просто, если однозадачная программа вообще ничего, кроме последовательной записи в файл, не делает. А если делает? Морозить все остальные операции, пока софтина не запишет строчку в лог?

Вот такие «программисты» писали системный интерфейс копирования в Windows. Когда на любую ошибку файл можно только качать заново

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

А теперь возьми какую-нибудь macros-heavy либу, и потеряй всю свою простоту одним махом.

Типа? Пример, если можно. Линукс кернель подойдёт? Там много макросов. И ничего, люди пишут код под него.

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

То есть, ты не знаешь.

mv ★★★★★ ()
Ответ на: комментарий от byko3y

Уже есть генераторы биндингов для питона, которые позволяют дергать функции C/C++ с минимумом ручного кодописания.

Меня не волнует количество ручного кодописания, я говорю про зерокост, про отсутствие накладных расходов на вызовы. Не говоря уже о том, что вызовы непрозрачны и сводят все к примитивному сишному интерфейсу. Функции С++ из питона дергать принципиально невозможно, потому что нет способа выразить на питоне логику шаблонов. Существующие способы позволяют максимум автоматизировать мономорфизацию.

Наследование же. То, с чего кресты начинали свое гниение в мире.

Это лишь один из механизмов абстракции крестов. Гибкость/негибкость – спорный вопрос.

Логично, что они должны быть либо прибиты гвоздями к компилятору, аки Go,

Не та философия языка.

либо я должен иметь возможность хранить в компонентах Qt какой-нибудь UCS-4.

Я бы хотел иметь возможность, как в Haskell, делать overloaded strings. Впрочем, с С++20 constexpr многие вещи значительно упрощаются, и многие вещи принципиально возможно сделать с помощью user-defined literals.

Siborgium ★★★ ()
Ответ на: комментарий от mv

Типа? Пример, если можно. Линукс кернель подойдёт? Там много макросов. И ничего, люди пишут код под него

Да в принципе и ядро линя тоже вполне себе пример. Вот еще из широко применяемого:

https://docs.gtk.org/gobject/#function_macros

byko3y ★★★ ()
Ответ на: комментарий от Siborgium

Это лишь один из механизмов абстракции крестов. Гибкость/негибкость – спорный вопрос

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

Логично, что они должны быть либо прибиты гвоздями к компилятору, аки Go,

Не та философия языка
Я бы хотел иметь возможность, как в Haskell, делать overloaded strings. Впрочем, с С++20 constexpr многие вещи значительно упрощаются, и многие вещи принципиально возможно сделать с помощью user-defined literals.

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Нет там никакой «семантики», есть только висящая в непонятно каком контексте переменная.

В лиспе это выглядит так

(let ((*param* val))
  (здесь какой-то код))

В Racket это выглядит так

(parameterize ([param val])
  (здесь какой-то код))

Поэтому контекст всегда очевидный. И в результате тот же stdout (в CL standard-output, в Racket - current-output-port) можно менять в рамках отдельных функций или потоков, а не создавать ради этого отдельные процессы.

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

В UNIX единица многозадачной программы — процесс, а не поток. Поток — это оптимизация для производительности с ручным разделением конфликтов и пониженной надёжностью.

Ты можешь вспомнить мутексы, а я могу вспомнить вопрос «зачем мне stdout тогда, если мне приходится еще и руками синхронизацию поверх него сооружать?».

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

monk ★★★★★ ()
Ответ на: комментарий от byko3y

Сколько там он десятков тысяч запросов в секунду обрабатывает?

Так это от железа зависит. Знаю, что быстрее, чем sendmail (это было заметно). Бенчмарка по всем серверам не видел.

monk ★★★★★ ()
Ответ на: комментарий от byko3y

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

Возможность выбрать Retry часто помогала. А сейчас помогает вариант Ignore, когда программа банковский ключ ищет на дисках последовательно, а дисководов в компьютере нет.

Вот. А мог бы показывать окошко «Abort, Retry, Ignore?». На сервере без монитора самое оно.

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

parsed parse_log(char* text, error_handler handler)
{
   if (well_formed_log_entry_p(text))
     parse(text)
   else if(handler) { 
     error_action action = handler(text));
     if (action.case == REPARSE) {
       return parse_log(action.text);
     } else if (action.case == USE_VALUE) {
       return action.value;
     }
   }
}

Морозить все остальные операции, пока софтина не запишет строчку в лог?

Так альтернатива замораживанию падение программы.

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

Для решения любой задачи достаточно машины Тьюринга. С ними такие задачи решаются проще.

Вообще в CL многие вещи именно решаются проще. CLOS автоматически позволяет расширять любой метод не меняя определения класс. Динамические переменные позволяют определять контекст не таская его за собой в объекте какого-нибудь класса. Замыкания позволяют передать в функцию действие так же легко как и любое значение. Нормальные исключения позволяют отложить позволить переопределить решение об обработке любой исключительной ситуации пользователю библиотеки (или даже пользователю программы, так как программа не падает, а запускается отладчик с возможностью выбрать рестарт).

monk ★★★★★ ()
Ответ на: комментарий от byko3y

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

Сделаешь наследника класса строки с другой реализацией. Что мешает?

monk ★★★★★ ()
Ответ на: комментарий от byko3y

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

…то ты можешь просто сделать свои строки и использовать их. Я не просто так отсылал к Qt и Folly.

Да, философия крестов — это иметь чудовищных размеров нечитаемую, но зато «расширяемую» библиотеку

Нет, философия крестов – предоставить минимальные строки, с которыми можно будет работать как со строками. Unicode легко добавляется оберткой std::string, как это сделано, например, в gtkmm.

Siborgium ★★★ ()
Ответ на: комментарий от fsb4000

Все стандартные библиотеки это делают :(

Если семантика оператора не меняется, это нормально.

А изменение смысла произошло только в iostream. Хотя нет, ещё в string ‘+’ приобрёл новый смысл и потерял коммутативность.

monk ★★★★★ ()
Ответ на: комментарий от monk

И в результате тот же stdout (в CL standard-output, в Racket - current-output-port) можно менять в рамках отдельных функций или потоков, а не создавать ради этого отдельные процессы

Зачем вообще это делать? В CL/Racket есть проблемы с выводом в файловый дескриптор? Пока что я вижу, что ты мужественно придумываешь проблему, которую потом так же мужественно решаешь.

Ну типа какой-то там код не умеет принимать локальные параметры, а умеет работать только с глобальными переменными, и теперь мы подставляет локальные значения в глобальные переменные — пардон, а зачем этот код изначально было так писать? В Си это было сделано потому, что у него нет архитектуры и от сделан «на отлюбись» — потому при конвертации однопоточной функции в многопоточную вылазят мьютексы для доступа к единственной глобальной переменной или TLS для как бы локализации этой глобальной переменной, и прочие радости жизни. Если в языке сразу есть возможность передачи контекста без костылей с try-finally — почему это не сделать сразу?

Почему широкий выбор костылей является преимуществом языка, вместо того, чтобы считать необходимость этих костылей недостатком языка? Ну типа в хаскеле есть близкая по сути проблема «как проносить контексты через монады и проносить его в чистые функции?» — но эту проблема хаскель создал сам себе. Кто заставлял CL и Racket эту проблему себе создавать?

В UNIX единица многозадачной программы — процесс, а не поток. Поток — это оптимизация для производительности с ручным разделением конфликтов и пониженной надёжностью

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

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

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

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

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

Да потому что не было никакой многопроцессовости — просто запускался новый процесс, который считал себя единственным в системе, отлаживался тоже как единственный в системе, он не мог никак взаимодействовать с остальными процессами. А когда у тебя процесс все-таки нуждается во взаимодействии с другими процессами, то выясняется, что инструменты IPC в классических никсах крайне убоги (файл, сокет, мейлбокс), и при реализации своей многозадачи ты семьсот раз их проклянешь. Как вызвать функцию в другом процессе? Как организовать доступ к общим данным? Копировать гигабайты взад-вперед? Продвинутая синхронизация на футексах и eventfd появились только в лине, фичу быстрого переключения контекста между процессами реализовали в гугле для линя, но в апстриме ее отклонили (она нужна потому, что в норме планировщик кидает второй процесс на другое ядро).

byko3y ★★★ ()
Ответ на: комментарий от monk

Так это от железа зависит. Знаю, что быстрее, чем sendmail (это было заметно). Бенчмарка по всем серверам не видел

https://www.coker.com.au/postal/postal-osdc.html

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

byko3y ★★★ ()
Ответ на: комментарий от monk

Возможность выбрать Retry часто помогала. А сейчас помогает вариант Ignore, когда программа банковский ключ ищет на дисках последовательно, а дисководов в компьютере нет

Что ты там за засохшее дерьмо мамонта шупаешь?

Морозить все остальные операции, пока софтина не запишет строчку в лог?

Так альтернатива замораживанию падение программы

Альтернатива замораживанию — корректная обработка ошибок. Если программи не умеет обрабатывать ошибки, то программа не умеет обрабатывать ошибки, и потому она падает. Когда нужно вставить диск — это еще решаемо, но на современных компьютерах уже давно основные накопители несъемные.

CLOS автоматически позволяет расширять любой метод не меняя определения класс

Много где есть сходные фичи

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

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

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

Опять-таки во многих языках это есть. Теперь подобная фича есть даже в C++.

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

Как ты думаешь, почему почти все языки последних лет отказываются от исключений? Может быть потому, что исключения — это исконно лисповый костыль, который применялся потому, что в лиспе не было инструментов для корректной передачи и обработки этих ошибок?

byko3y ★★★ ()
Последнее исправление: byko3y (всего исправлений: 2)
Ответ на: комментарий от monk

Сделаешь наследника класса строки с другой реализацией. Что мешает?

Мешает то, что Qt использует реализацию строк, а не просто интерфейс. Потому возможности расширения если и есть, то очень небольшие.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Что ты там за засохшее дерьмо мамонта шупаешь?

ibank2.ru

Альтернатива замораживанию — корректная обработка ошибок.

Как должна быть обработана ошибка недостатка места на диске где-то в сериализаторе?

но на современных компьютерах уже давно основные накопители несъемные

И с них можно перекинуть информацию на другой носитель и продолжить операцию.

Много где есть сходные фичи

Например?

А вместо этого костылить временной изменение этой переменной в try-finally

В смысле, ты это предлагаешь, костылить? Если есть динамические переменные, то есть гарантия локальности изменения. Ты же не костылишь вручную восстановление переменных окружения после запуска подпроцесса — ОС гарантирует.

Опять-таки во многих языках это есть.

Уже да. Но в лиспе появилось намного раньше.

Теперь подобная фича есть даже в C++.

Захват локальной переменной плохо работает, потому что сборщика мусора нет.

Как ты думаешь, почему почти все языки последних лет отказываются от исключений? Может быть потому, что исключения — это исконно лисповый костыль, который применялся потому, что в лиспе не было инструментов для корректной передачи и обработки этих ошибок?

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

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

monk ★★★★★ ()
Ответ на: комментарий от byko3y

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

Я go не знаю, но как же аллоцируются объекты которые должны вернуться из функции? Как например в factory method.

Работет механизм похожий на RVO в cpp? Когда аллокация в стеке происходит до вызова функции сразу под возвращаемое значение? Я так понимаю никакого динамического связывания интерфейсов и имплементаций в го нет, во многих случаях размеры необходимой области можно найти в compile time, но если вернуть коллекцию то без аллокация в куче не обойтись, размер ведь заранее нельзя угадать.

Aber ★★★★ ()
Последнее исправление: Aber (всего исправлений: 2)
Ответ на: комментарий от byko3y

Зачем вообще это делать? В CL/Racket есть проблемы с выводом в файловый дескриптор? Пока что я вижу, что ты мужественно придумываешь проблему, которую потом так же мужественно решаешь.

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

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

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

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

Кооперативная многозадачность — это самый производительный способ реализации многозадачности, был и остается до сих пор. А как это сделать без потоков?

В Racket кооперативная многозадачность без потоков. Планировщик внутри процесса.

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

Так многопроцессы гораздо проще и логировать (syslog) и отлаживать (gdb с процессом гораздо лучше работает, чем с потоком).

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

Это лучше, чем блокировки на каждый чих.

Как вызвать функцию в другом процессе?

onc rpc

Как организовать доступ к общим данным? Копировать гигабайты взад-вперед?

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

monk ★★★★★ ()
Ответ на: комментарий от monk

Напоминаю классическое «Abort, Retry, Ignore?». И это было до того, как решили, что вместо предложения выбора надо бросать исключение.

Как будто оно когда-то нормально работало.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Как будто оно когда-то нормально работало.

Если хоть немного понимал, на чём программа стопорнулась, работало нормально. Кончилось место? Почистил. Retry. Пытается перезаписать уже существующую открытую библиотеку или прочитать несуществующую букву диска? Ignore. А Abort работает как сейчас.

monk ★★★★★ ()
Ответ на: комментарий от monk

А изменение смысла произошло только в iostream. Хотя нет, ещё в string ‘+’ приобрёл новый смысл и потерял коммутативность.

Ещё, как минимум, path есть.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от monk

Что ты там за засохшее дерьмо мамонта шупаешь?

ibank2.ru

Не знаю, не пользовался, но сайт у них — это какая-то дичь.

Как должна быть обработана ошибка недостатка места на диске где-то в сериализаторе?

У тебя сериализатор сам пишет в диск? Тогда у меня для тебя плохие новости. Как правило, запись на диск сама по себе достаточно замороченная штука, чтобы ее нужно было необходимо реализовывать независимо от алгоритмов обработки данных. То есть, сериализатор пишет в буфер, буфер сбрасывается на диск. Даже в СУБД работа с диском, как правило, отделена от работы с данными, хотя, казалось бы, СУБД больше ничего не делает, кроме чтения-записи на диск.

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

но на современных компьютерах уже давно основные накопители несъемные

И с них можно перекинуть информацию на другой носитель и продолжить операцию

Нет, они достаточно большие, чтобы проблема переполнения диска на практике не встречалась вообще.

Много где есть сходные фичи

Например?

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

В смысле, ты это предлагаешь, костылить? Если есть динамические переменные, то есть гарантия локальности изменения. Ты же не костылишь вручную восстановление переменных окружения после запуска подпроцесса — ОС гарантирует

По своим функциям это чисто локальные переменные, с потенциальным умолчательным значением. Так почему они не описываются как локальные переменные? Ответ — потому что в лиспе были или до сих пор есть убогие механизмы передачи контекста. К слову про «до сих пор» — кложа использует динамические биндинги для потокоспецифичных значений, но поскольку потоки активно лазят друг к другу, то внезапно выясняется, что при выполнении в контексте другого потока код делает совсем не то, что должен был:
https://clojure.org/reference/vars#conveyance
Что делаем? Копируем всем скопом динамические переменные в другой поток при вызовах определенных функций. Что делать, если нужно все-таки прочитать контекст исходного потока — мне страшно подумать.

Собственно, да — как ты собрался писать асинхронщину/зеленые потоки на динамическом связывании? Это прямо-таки ультимативный пример случая «вызываемая функция внезапно переписала мои переменные как ей захотелось». Она-то могла из try-finally еще не выйти, потому даже после окончания вызова этой асинхронной функции значение осталось «испорченным».

Здесь у лиспа торчат уши однозадачного выполнения — как в упомянутом предыдущим сообщением языке Си. Можно ли это ставить в вину языку, которому 50 лет? Конечно же нет. Повод ли это для того, чтобы закопать его? Вполне себе да, вместе с сихой, которая тоже крайне неудобна в многозадачности, как и ее дружаня юникс с его сигналами и форками, которые тоже давно пора закапывать, поскольку они мешают писать асинхронный и многопоточный код.

Опять-таки во многих языках это есть.

Уже да. Но в лиспе появилось намного раньше

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

Захват локальной переменной плохо работает, потому что сборщика мусора нет

Ну елы-палы, хватай значение или shared_ptr.

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

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

byko3y ★★★ ()
Ответ на: комментарий от Aber

Я go не знаю, но как же аллоцируются объекты которые должны вернуться из функции? Как например в factory method

Не спрашивай — я не знаю.

byko3y ★★★ ()
Ответ на: комментарий от monk

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

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

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

Какая разница, испортится ли внутреннее состояние в одном процессе сервера БД или всех процессах? У тебя логика плана «потерять две ноги хуже, чем потерять одну». А если я хочу оставить обе ноги?

В Racket кооперативная многозадачность без потоков. Планировщик внутри процесса

И ты мне скажешь, что Racket умеет в вызовы функций между задачами с околонулевыми накладными расходами?

Так многопроцессы гораздо проще и логировать (syslog) и отлаживать (gdb с процессом гораздо лучше работает, чем с потоком)

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

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

Это лучше, чем блокировки на каждый чих

Зачем блокировать? Даже системные вызовы не обязательно делать — смотри io_uring.

Как вызвать функцию в другом процессе?

onc rpc

Это совершенно иная весовая категория тяжелющих запросов по сети.

Как организовать доступ к общим данным? Копировать гигабайты взад-вперед?

Есть разделяемая память, если общих данных гигабайты

Настолько «есть», что даже во фряхе 12.0 нет инструментов для управления чужой разделяемой памятью, забытой процессами. При том, что та же винда просто высвобождает разделяемую память с последним использующим ее процессом.

byko3y ★★★ ()
Ответ на: комментарий от Aber

Я go не знаю, но как же аллоцируются объекты которые должны вернуться из функции? Как например в factory method.

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

alysnix ()
Ответ на: комментарий от byko3y

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

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

Нет, они достаточно большие, чтобы проблема переполнения диска на практике не встречалась вообще.

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

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

При чём тут новые классы в рантайме? Можешь добавить на string.Formatter.parse действие перед или после того, которое прописано в описании класса? В CLOS можно.

Так почему они не описываются как локальные переменные? Ответ — потому что в лиспе были или до сих пор есть убогие механизмы передачи контекста.

У кого не убогие? Как должно быть правильно?

Что делать, если нужно все-таки прочитать контекст исходного потока — мне страшно подумать.

Передавать ссылку на него в параметре.

Собственно, да — как ты собрался писать асинхронщину/зеленые потоки на динамическом связывании? Это прямо-таки ультимативный пример случая «вызываемая функция внезапно переписала мои переменные как ей захотелось». Она-то могла из try-finally еще не выйти, потому даже после окончания вызова этой асинхронной функции значение осталось «испорченным».

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

Примерный аналог динамических переменных - передача параметров по значению.

def thread_function(x):
    x = x + 1
    time.sleep(2)
    ...

x = 5
threading.Thread(target=thread_function, args=(x,))
time.sleep(1)
print x # напечатает 5, хотя внутри другого потока x = 6

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

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

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

monk ★★★★★ ()
Ответ на: комментарий от byko3y

нужно иметь их где-то в корне, с явной обработкой

Можно пример кода? Пусть даже на примере сериализации.

лисп

(defun serialize (obj)
   ...
   (buffer-write obj-bytes)
   ...)

(defun buffer-write (bytes)
   ...
   (system-write block)
   ...)

(let ((*standard-output* (get-store-place)))
  (serialize my-obj))

питон

def serialize(obj, place):
  ...
  buffer_write(obj_bytes, place)
  ...

def buffer-write(bytes, place):
  ...
  system_write(block, place)
  ...

serialize(my_obj, get_store_place())

Как не протаскивать place через аргументы всех промежуточных функций до самого низа? В лиспе serialize вообще не обязан знать, какую переменную buffer_write будет использовать для записи объекта - его дело подготовить поток байтов и отправить из на запись. В следующей версии может появиться возможность указать размер буфера, код serialize не поменяется, а поменяется только код buffer-write и у пользователя появится возможность указать ещё один параметр.

monk ★★★★★ ()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)