Читать в телеге. Когда-то там были посты не только от меня.
Мои впечатления от солвера 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 их просто сделал более попсовыми.
Удаленная работа с docker
С сервисом докера общение идет через сокет, т.е. ничего не мешает с ним общаться по сети, например, через ssh. Простейший вариант — установить переменную окружения DOCKER_HOST как ssh://${USER}@${HOST}. После этого все докер команды будут выполняться на удаленной машине.
Можно немного заморочиться и использовать контексты, переключаясь между ними.
Это может быть удобно во всяких скриптах деплоя, например в gitlab-ci. Вместо заворачивания в скрипт или добавления перед каждой командой ssh user@host... просто писать команды и все. Другое преимущество — переменные окружения будут браться локально, а еще локально будут копироваться артефакты через
docker cp container:/container/path local/path.
Модель акторов для построения бигдаты
А вот и доклад посвежее про Erlang, полуторагодичной давности. В нем кратко объясняется идея акторов и рассказывается о системе учета пациентов, построенной на этой модели. В какой-то момент там внезапно появляются фильтр Блума, рандеву-хеширование, алгоритмы консенсуса и выбора лидера.
Стоит отметить, что система не является основным источником информации (а только для всякой бигдаты), поэтому вопросы того, как делать все транзакционно, тут не поднимаются (впрочем, пациенты довольно изолированы друг от друга, так что это ок, наверно). А так получается практически event sourcing.