Читать в телеге. Когда-то там были посты не только от меня.
Проверка наличия команды
Оказывается, в баше есть встроенная команда 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 их просто сделал более попсовыми.