Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Проверка наличия команды

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

Более очевидный способ — использовать which, который еще и путь покажет, но внезапно, это не самый лучший вариант:

  • which может отсутствовать в некоторых системах (особенно если система урезана под контейнер);
  • which на некоторых системах может не устанавливать код ошибки;
  • which может вызывать под капотом пакетный менеджер.
СсылкаКомментировать

Редактирование команды в редакторе

Чтобы отредактировать и выполнить длинную команду-колбасу с помощью редактора, можно воспользоваться Ctrl+X+E. Мне это пригодилось для длинных curl.

В редакторе может не быть включен мягкий перенос строк, тогда пользы будет немного. В nano это решается Esc+$ или добавлением set softwrap в ~/.nanorc.

СсылкаКомментировать

Мои впечатления от солвера z3

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

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

В целом сама парадигма довольно прикольная: надушить четкое ТЗ и дело в шляпе (а недостаточно душнишь — держи тривиальное решение с нулями). Какое-нибудь судоку решать с помощью него — самое то, фигачится минут за 10. Да и задача о рюкзаке была реализована мной за примерно такое же время — это не динпрог вертеть.

Однако чудес солвер тоже не открыл. Магический квадрат для 4x4 он находит мгновенно, а вот 5x5 уже не смог (я не стал ждать больше 10 минут). При этом проц и память были вообще не нагружены, т.е. по умолчанию параллелизации нет. И рюкзак для «плохих» входных данных (где стоит приближенно решать) тоже быстро не отработал.

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

СсылкаКомментировать

Обманчивая простота API Redis

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

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

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

СсылкаКомментировать

Понятность ошибок компилятора

Забавный пост-сравнение степени подробности ошибок в разных языках. В целом совпадает с моими впечатлениями (на всем этом мне довелось что-нибудь написать). Elm тут немного перехвалили, хотя когда писал на нем, подсказка про возможные опечатки была весьма полезна. Про Rust тоже писал, что ошибки классные, когда компилятор угадал в чем ты ошибся, если нет, то все грустно.

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

СсылкаКомментировать

Передача данных — убийца производительности

Занятная статья про проблемы производительности. В основном это выжимка из доклада одного из создателей BLAS и LAPACK (на которые полагается любимый многими numpy, если кто не в курсе). Сам доклад довольно неспешный, первые минут 15 там вообще идут биографические заметки, все основное в статье изложено неплохо.

TLDR: По CPU уже намасштабировались от души, сейчас основная проблема — передача данных. Лучшие компы работают на 5% мощности из-за того, что долго ждут на передаче данных. Раньше можно было сделать 1 вычисление с плавающей точкой на 1 перемещение, сейчас — уже 10-100. При этом очень мало внимания уделяется проектированию ПО так, чтобы оно учитывало особенности железа. Сейчас вполне нормально ситуация, когда асимптотически неэффективый алгоритм будет работать в разы быстрее оптимального просто за счет того, что ему нужно меньше перемещений данных.

СсылкаКомментировать

Обновление версий библиотек в Gradle

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

В Gradle для этого есть несколько плагинов. Основной просто покажет отчет с последними версиями для всех библиотек. Там же есть и ссылки на плагины для автоматического обновления в скрипте билда.

Небольшая проблема в том, что указать версию библиотеки можно кучей разных способов. Простейший — в build.gradle или build.kts. При этом там же ее можно вынести в переменную или в какую-нибудь функцию. Другой способ — каталоги версий, когда версии хранятся в отдельном файле settings.kts или вообще в toml. Разные плагины для автоматического обновления поддерживают разные варианты описания версий — один только в основном скрипте, второй — только в каталогах версий. Первый я попробовал на своем древнем поделии, и там все нормально обновилось (кроме npm, конечно).

Автоматическое обновление имеет минусы. Во-первых, подсовываются иногда beta и RC версии. Во-вторых, breaking changes и вообще списки изменений надо читать самому. Но так-то в проекте все хорошо покрыто тестами и можно не париться, правда же? :)

В Gradle еще есть возможность указать диапазоны версий, например, 1.7.+ — почти как в npm. Но тут я не могу придумать вариант, когда привязка к последней версии лучше, чем воспроизводимая сборка с фиксированными версиями.

СсылкаКомментировать

Список измененных файлов в ветке

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

git diff-tree --no-commit-id --name-only -r %base_commit_hash% -r %commit_hash%

В качестве %base_commit_hash% и %commit_hash% можно использовать как хэш коммита, так и название ветки (origin/master, например). В CI почти наверняка эти ревизии есть в переменных окружения. В GitLab это CI_MERGE_REQUEST_DIFF_BASE_SHA и CI_COMMIT_SHA.

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

git merge-base master HEAD 

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

СсылкаКомментировать

Тьюринг-полнота почти везде

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

TLDR: почти любая система с вводом может быть Тьюринг-полной, и легче построить Тьюринг-полную систему, чем неполную. Есть довольно много способов построить простенький вентиль (с помощью поиска и замены, регулярок, разного времени выполнения операций, правил игр и т.д. и т.п.), а там уже и до прочих вычислений недалеко. В каких-нибудь песочницах или компонентах языка программирования (например, в каких-нибудь шаблонах, printf или mov) это вообще элементарно. Все это имеет веселые последствия для ИБ: потенциально в любой “песочнице”, даже сильно зарегулированной, можно исполнять произвольный код, не выходя из нее. А если еще взять во внимания всякие Row hammer и Spectre, то получается, что можно выполнять произвольный код на хосте, не выходя из песочницы. Хотя есть вялая надежда на языки, которые нарочно не Тьюринг-полные, например, тотальное ФП.

СсылкаКомментировать

let и also в Scala

… давно есть. Аналог let — это pipe:

someCollection
  .map(pewpew)
  .pipe(all => if (all.size % 2 == 0) all.drop(1) else all)
  .sum

а замена also — это tap:

someCollection
  .filter(ewew)
  .map(pewpew)
  .flatMap(...)
  .tap(println) // debug
  .reduce(...)

В статье, кстати, они сравниваются с башевскими | и tee, а также с |> из F#. Так что сама идея этих функций далеко не нова, Kotlin их просто сделал более попсовыми.

СсылкаКомментировать