LINUX.ORG.RU

Vim или Emacs? А LISP в 2021?

 , ,


0

2

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

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

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

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

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


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

О нееет. Это же надо один раз vimrc скопировать. Это же так сложно. Ведь нельзя сразу в docker образе твоих серверов vim сконфигать. Технологии до этого еще не дошли.

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

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

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

Раскрой мысль.

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

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

О нееет. Это же надо один раз vimrc скопировать. Это же так сложно

Ты совершенно прав. Я выше сказал - вы далеки от настроек под себя, а теперь рассказываете. Vim для быстрой починки после изменений под себя не годится. Есть другие методы.

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

Угу. Что правильней, молоток или отвёртка?

Молоток, конечно. Те кто использует отвёртки, просто не осилили молоток.

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

Все параметры с дефолтными значениями надо вытаскивать на уровень выше

Да, ок.

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

Это если оно нужно, что всё-таки далеко не каждый раз происходит.

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

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

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

Есть два инструмента общего назначения: нож и топор. Что правильней?

А насчёт явного возврата, ошибки проблема не только в том, что кода больше, но и в том, что на каждом уровне надо знать о том, что внутренняя функция возвращает ошибки и какие именно. В результате получаем сильную зависимость от реализации: поменяли m[1000] /* вроде хватит */ на m = new ... — плюс одна ошибка, добавили вычисление с делением — вторая ошибка, используем библиотеку работы с температурой — могут быть ошибки проверки диапазона. И дальше либо эти ошибки игнорируются, либо все они должны быть проброшены на уровень выше.

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

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

Это если оно нужно, что всё-таки далеко не каждый раз происходит.

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

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

Причём узнать о том почему оно не работает можно только прочитав исходники.

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

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

Гибкость есть в том случае, если программист намеренно её не отключил. По-умолчанию. Чтобы её отключить надо приложить целенаправленные усилия.

То есть, аргумент «можно всё настроить не патча код» не (всегда) работает.

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

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

Есть два инструмента общего назначения: нож и топор. Что правильней?

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

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

Это не только и не столько проблема, сколько преимущество. Ну не будет средний разработчик разбираться какое исключение может из функции прилететь, особенно если документации нет. Когда упадёт, тогда и будет добавлен catch. Сразу видно, что случиться может - это заставляет сразу продумать что и как обрабатывать. К слову, когда тебе через N уровней прилетает какой-нибудь NullPointerException, то удачи обработать его как-то умно.

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

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

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

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

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

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

Уточню: вот есть библиотека и из неё экспортируется функция do_something, которая вызывает do_something_impl. Можно будет подменить только do_something_impl так, чтобы do_something вызывала нашу новую функцию?

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

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

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

это вопрос лишь архитектуры.

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

Сразу видно, что случиться может - это заставляет сразу продумать что и как обрабатывать. К слову, когда тебе через N уровней прилетает какой-нибудь NullPointerException, то удачи обработать его как-то умно.

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

А вот писать вместо

return a.f[5] + b.g[8];

что-то вроде

af = a.f;
if error?(af): return af.at("a.f");
af5 = af[5];
if error?(af): return af5.at("a.f[5]");
bg = b.g;
if error?(bg): return bg.at("b.g");
bg8 = bg[8];
if error?(bg8): return bg8.at("b.g[8]");
return af5 + bg8;

очень неудобно.

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

Можно будет подменить только do_something_impl так, чтобы do_something вызывала нашу новую функцию?

Да. Исключение, если do_something_impl является макросом, а не функцией, тогда do_something надо будет перезагрузить.

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

это вопрос лишь архитектуры.

Это понятно. Настоящий программист может написать программу на фортране на любом языке.

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

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

Из него можно, как минимум, получить строку, где упало, а часто и контекст.

И? Адекватно обработать в рантайме это никак не поможет. Всё, что это даст - подскажет куда в исходники смотреть. То есть, как я и говорил, нормальная обработка ошибок будет появляться после хождения по граблям.

Кстати, в С++ какой-нибудь std::logic_error даже строку не скажет.

В CL можно даже продолжить выполнение.

Да, но, опять же, это подходит только для девелоперского окружения.

очень неудобно.

Ну это ты какой-то ужас показал, но в эту игру можно играть вдвоём:

X af5;
try
{
    af5 = a.f[5];
}
catch(const my_error& e)
{
    af5 = 15;
}
catch(const std::exception& e)
{
    af5 = 10;
}
catch(...)
{
    // Пишем в лог "всё плохо!"
    throw my_other_exception(...);
}

Y bg8;
try
{
    bg8 = b.g[8];
}
catch(const my_error& e)
{
    bg8 = 20;
}
catch(const std::exception& e)
{
    bg8 = 1;
}
catch(...)
{
    throw my_other_exception(...);
}

Кошмар!

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

Да, но, опять же, это подходит только для девелоперского окружения.

Почему? Например, надо прочитать данные из CSV с испорченными строками. Можно указать, что их пропускать.

но в эту игру можно играть вдвоём

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

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

Например, надо прочитать данные из CSV с испорченными строками. Можно указать, что их пропускать.

Ну да, это канонический пример. (:

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

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

И всё же твой пример был достаточно гиперболизирован. При наличии минимального сахара (ну скажем «как в расте») не так оно и страшно смотрится: будут вопросики в коде и всё. Синтаксический ли это мусор или наглядное указание потока выполнения - вопрос спорный.

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

Вопрос в том насколько часто оно нужно и стоит ли эту машинерию тащить на уровень языка.

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

Наверное, единственная вещь, которую не добавили в Common Lisp — это продолжения. Зато её добавили в Racket.

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

При наличии минимального сахара (ну скажем «как в расте») не так оно и страшно смотрится: будут вопросики в коде и всё.

В расте выход за границы массива кидает исключение (то есть панику). И вообще там отношение к исключением забавное. С одной стороны, они есть и даже есть свой try/catch (panic::catch_unwind). С другой стороны, пропагандисты активно делают вид, что исключений нет.

Правильный язык без исключений - Zig. Но там их просто сделали UB: https://ziglang.org/documentation/0.8.1/#Undefined-Behavior

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

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

Тут я согласен.

И вообще там отношение к исключением забавное. С одной стороны, они есть и даже есть свой try/catch (panic::catch_unwind). С другой стороны, пропагандисты активно делают вид, что исключений нет.

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

В расте выход за границы массива кидает исключение (то есть панику).

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

Правильный язык без исключений - Zig.

Правильно я понимаю, что вся «правильность» заключается в том, что в Zig никак нельзя перехватить падение? Потому что насколько я вижу, в остальном там выход за границы выглядит точно так же. Ну и «пользовательское UB» похоже приходится через unreachable костылить. Если так, то не готов считать это преимуществом. Да, это точно заставит не злоупотреблять «паникой», но вижу тут больше ограничений, чем преимуществ.

И что будет, если отключить «проверки безопасности»? Настоящее UB?

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

Язык определяет параметры программы по-умолчанию. Если программа на Java, она будет разбита на классы, если программа на Haskell, в ней будет отделён ввод-вывод от основного алгоритма, если программа на C++, она будет использовать RAII, если программа на Common Lisp, в ней будет отладчик

Чем больеш в ЯП инструментов — тем меньшее кол-во онных будет использовано в проде. Особенно это касается C++.

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

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

Попробуй написать что-то, что будет менять данные из вызывающей функции

В смысле, изменять переданную структуру? Тоже работает.

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

Можно подробнее? Или пример? Чувствую непонимание (то ли я тебя, то ли ты меня)

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

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

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

interrupt-thread не грохает поток, а выполняет функцию в контексте потока. Там можно и состояние проверить и поменять что надо

Какая разница? Результат один — непредсказуемое состояние данных потока.

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

А кто это должен делать? Пользователь сериализатора каждый раз велосипедить?

Ну что ты как ребенок — сделай библиотечную функцию.

Модульность – это когда сериализатор внутри разбит на модули, а не когда вместо решения дают полуфабрикат

Минутка ликбеза:

https://ru.wikipedia.org/wiki/Модульность
«Модульность — это свойство системы, связанное с возможностью её декомпозиции на ряд внутренне связанных между собой модулей.»

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

Если сериализация и запись в файл независимы друг от друга и могут быть по-разному состыкованы — это модульность. Если сохранение в файл встроено в сериализатор — это монолит.

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

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

Статические анализаторы легко проверяют. Даже тупо grep ‘\*\S*\*’ по телу функции достаточно

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

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

Но ведь не делают нормальный, а то вдруг где-то используется

Сделали уже давно «===».

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

Для работы на удаленном сервер vim #1. Нет ничего удобнее

Ты правишь код на удаленном сервере? В чем прикол? Поправить конфиги можно и в Nano.

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

Ведь нельзя сразу в docker образе твоих серверов vim сконфигать. Технологии до этого еще не дошли

Ы-ы-ы-ы, но работать-то ты будешь с моими docker-образами.

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

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

Для new() есть вариант с Option?

Правильно я понимаю, что вся «правильность» заключается в том, что в Zig никак нельзя перехватить падение?

Также как в Си.

И что будет, если отключить «проверки безопасности»? Настоящее UB?

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

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

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

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

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

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

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

Я по этой же причине не люблю питоньи генераторы — они от лукавого, я считаю. Если ты делаешь какой-то длинный асинхронный алгоритм — будь добр приделать к нему явную обработку ошибок и возможность остановить его работу. Ты все равно будешь делать то же самое, но можно аккуратно и чисто сделать эти фичи явными, а можно городить костыли вроде interrupt-thread-ов или питоньих send/throw/close.

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

Сделать зависимость плоской, вместо

def func(arg):
    return func_nested(arg+arg+arg)

func_nested = nested_func
func(somevalue)

писать

def func(arg, cb):
    return cb(arg+arg+arg)

print(func(somevalue, nested_func))

Надо передать опциональные параметры в nested_func?

param = 456
def modified_nested_func(arg):
    return nested_func(arg, param)

print(func(somevalue, modified_nested_func))

И внезапно всё решилось статичными лексическими привязками.

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

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

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

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

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

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

Ты перед тем, как bash или coreutuls обновить, перезагружаешься в single-mode? Ведь обновляя coreutils ты именно меняешь алгоритмы сохраняя состояние (какой-нибудь скрипт может в первой строке cat запустить из старой версии, а в сотой уже из новой).

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

Ну что ты как ребенок — сделай библиотечную функцию.

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

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

Чем больеш в ЯП инструментов — тем меньшее кол-во онных будет использовано в проде.

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

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

Ты перед тем, как bash или coreutuls обновить, перезагружаешься в single-mode? Ведь обновляя coreutils ты именно меняешь алгоритмы сохраняя состояние (какой-нибудь скрипт может в первой строке cat запустить из старой версии, а в сотой уже из новой)

Упомянутые тобой утилиты являются так сказать stateless — в этом плане они похожи на контейнеры в этих ваших докерах: после обновления какое-то время будут работать две версии утилит, и лишь после завершения работы старых версий останутся только новые версии. А вот всякие Smalltalk-и с лиспами и, если я не ошибаюсь, обероном как раз делают упор на состояние и его сохранение при перезагрузке — в 80-х это было круто и модно, пока не стало ясно, что это не работает.

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

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

Эта библиотечная функция будет уметь только создавать временный файл и комплиментарная ей функция будет уметь только перемещать временный файл в конечное назначение — они не будут ничего знать про сериализацию. Это в тему примеров:

Vim или Emacs? А LISP в 2021? (комментарий)
Vim или Emacs? А LISP в 2021? (комментарий)

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

А что делать, если потоков обработки несколько?

Какая разница? Динамический контекст для обработчика задаётся, а не для потока.

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

Чем лучше параметры в аргументах функции, чем эти же параметры в let/parameterize? Можешь считать, что с точки зрения функции просто передаётся (дополнительным аргументом) копия некоего объекта «окружение», в котором значения всех динамических переменных. Только с точки зрения производительности всё гораздо быстрее, так как реально копируются только изменения этого объекта.

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

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

Зависит от квалификации программиста. Если программист — желторотый новичок, то он будет применять абсолютно все фичи крестов. Если это матерый морской волк, то он будет писать на крестах, как на Си, но с замыканиями, выводом типов, RAII, а также небольшим количеством обобщений (из STL и не только, но 100% не из Boost).

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

А вот всякие Smalltalk-и с лиспами и, если я не ошибаюсь, обероном как раз делают упор на состояние и его сохранение при перезагрузке

Функция состояние не хранит. При обновлении лиспа также внутри одной программы будет две версии функции и новые вызовы пойдут к новой версии функции. Один-в-один bash. У smalltalk’а, кстати, аналогично: методы класса обновятся, но данные уже созданных объектов останутся старыми.

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

Зависит от квалификации программиста.

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

Могу предположить, что ты хотел сказать не «чем больеш в ЯП инструментов — тем меньшее кол-во онных будет использовано в проде», а «чем больеш в ЯП инструментов — тем меньшая доля онных будет использовано в проде».

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

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

То есть ты всё-таки настаиваешь, что пользователь библиотеки недостоин получить API вида serialize(obj, file), а обязан явно вызывать создание файла, открытие файла, сериализацию в открытый файл, закрытие файла, перемещение файла и обрабатывать исключительные ситуации удалением файла. И писать всю эту портянку всюду, где ему пожелается сериализовать объект в файл.

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

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

Как-то это противоречит «Кусок записи выполняет вложенная функция.». Если в функции написано «write(f, …)», то очень наивно ожидать, что размер f не поменяется. И любые подмены файла или изменения переданного изменяемого объекта должны быть документированы.

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

Паттерн-то хороший. Но попробуй таким образом написать сортировку массива.

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

При обновлении лиспа также внутри одной программы будет две версии функции и новые вызовы пойдут к новой версии функции. Один-в-один bash. У smalltalk’а, кстати, аналогично: методы класса обновятся, но данные уже созданных объектов останутся старыми

А что делать с данными, которые являются функциями? Такая архитектура требует очень жесткого разделения данные-алгоритмы, но даже в БД такое не всегда возможно. Это возможно только для четко определенных форматов-протоколов. Какие там инструменты для организации форматов в лиспе? Как там происходит трансляция между разными версиями данных? Дай отгадаю — никак. И получается, что без трусов, но в очках.

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

Зависит от квалификации программиста

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

Меньше — макросы не будут использоваться. А макросы — это жирная доля сихи. Большая часть инструментов крестов как раз и нужны для замены макросов.

Могу предположить, что ты хотел сказать не «чем больеш в ЯП инструментов — тем меньшее кол-во онных будет использовано в проде», а «чем больеш в ЯП инструментов — тем меньшая доля онных будет использовано в проде»

И это тоже.

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

пользователь библиотеки недостоин получить API вида serialize(obj, file), а обязан явно вызывать создание файла, открытие файла, сериализацию в открытый файл, закрытие файла, перемещение файла и обрабатывать исключительные ситуации удалением файла

Да. Однозадачные системы у которых есть максимум три накопителя «A:», «B:» и «C:» и нет сети остались в 80-х. Это, однако, не значит, что у тебя в конкретном приложении/либе/фреймворке не будет возможности организовать единственную функцию, которая будет добавлять в очередь главного цикл задачу сериализации с привязанной задачей сохранения в файл. Это лишь значит, что универсальная библиотека сериализации должна поддерживать многозадачное выполнение и большое разнообразие устройств ввода-вывода, оставляя реализацию планирования и ввода-вывода другим модулям.

Так делает zlib, так делают библиотеки шифрования, и потому прекрасно живут до наших дней. А представь, что zlib бы умело только читать-писать в файл. Прямо как многие никсовые утилиты, которым приходится ставить костыли где-то в /dev или создавать файлы сокетов, потому что они кроме файлов ни с чем не умеют работать.

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

А что делать с данными, которые являются функциями?

Смотря как являются. Если ссылкой на имя функции, то обновятся, если на значение, то останутся без изменений.

Как там происходит трансляция между разными версиями данных? Дай отгадаю — никак.

Для CLOS есть трансляция update-instance-for-redefined-class. Для всего остального (числа, строки, списки, массивы) не очень ясен термин «версия».

monk ★★★★★ ()

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

Патч конечно отправил.

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

А представь, что zlib бы умело только читать-писать в файл.

То всё-таки поточный архиватор, а не сериализатор.

А сериализатор вот: https://docs.python.org/3/library/shelve.html

Умеет писать только в файл. Авторы не правы?

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

Как-то это противоречит «Кусок записи выполняет вложенная функция.». Если в функции написано «write(f, …)», то очень наивно ожидать, что размер f не поменяется. И любые подмены файла или изменения переданного изменяемого объекта должны быть документированы

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

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

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

Паттерн-то хороший. Но попробуй таким образом написать сортировку массива

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

Я уже не помню, когда в последний раз что-то подобное писал — наверное, в рекурсивных вызовах промисов в JS, которые эквивалентны циклу async/await. В обоих упомянутых вариантах реализации этого алгоритма мне было достаточно лексических привязок (для промисов) и просто локальных переменных (для async/await) — зачем мне динамические привязки? Чтобы передать еще больше параметров функции сортировки? Я могу опять же передать их через замыкания.

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

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

Смотря как являются. Если ссылкой на имя функции, то обновятся, если на значение, то останутся без изменений

Так а замыкания как обрабатываются? Отдельно обновляется функция, отдельно — ее связанные данные?

Для CLOS есть трансляция update-instance-for-redefined-class. Для всего остального (числа, строки, списки, массивы) не очень ясен термин «версия»

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

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

А сериализатор вот: https://docs.python.org/3/library/shelve.html
Умеет писать только в файл. Авторы не правы?

Это не сериализатор — это компонент для работы с файлами. Сериализатор вот:
https://docs.python.org/3/library/pickle.html
Причем, это же написано на странице документации shelve.

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

Clojure поступает радикально — никаких подмен, только неизменяемые персистентные данные

В файлы писать тоже не умеет?

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

Ну не знаю. Всё равно надо читать в документации, что write изменяет размер файла, а close прекращает к нему доступ.

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

Например. Причём сортировка достаточно тривиальный алгоритм. А если чтение рекурсивной структуры типа XML с кучей параметров чтения? Тоже в каждый рекурсивный вызов все параметры переписывать?

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

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

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

Это не сериализатор — это компонент для работы с файлами.

Нет слов. Ну пусть будет

компонент_для_работы_с_файлами(obj, file)

во всех моих рассуждениях выше.

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