Читать в телеге. Когда-то там были посты не только от меня.
Проблемы монад и алгебраические эффекты
В продолжение про проверяемые исключения в Scala — очень доступный доклад про проблемы монад и способы их решения. Первые 26 минут можно посмотреть, если хотите освежить в памяти, что такое монада, и как она проникла в ваш любимый язык, докладчик рассказывает на простейшем примере. Но кто уже знаком с этой идеей, может смело мотать на середину.
Первая и основная на мой взгляд проблема, с которой сталкиваешься при композиции нескольких Either/Result’ов — общий тип для ошибок. Кстати, это одна из проблем, на которую я мужественно забил во время своих забав с elm. Кроме этого, функции приобретают цвета, а с типами вроде Either[Either[ClientError, ServerError], Response]
легко запутаться.
Одно из решений — Monad Transformers в Haskell, которые позволяют запихать одну монаду внутрь другой. Однако это довольно трудное для восприятия решение (судите сами) и типы все равно выглядят страшно.
Альтернативное решение — алгебраические эффекты. “Алгебраические” — потому что образуют алгебру, т.е. если функция f
имеет эффект A
, а функция g
имеет эффект B
, то любая их композиция будет иметь эффект C = A + B = B + A
, причем А + 0 = A
, где 0
— отсутствие эффекта. Алгебраические эффекты аналогичны по свойствам трансформерам, но должны быть проще для восприятия. И вариант их реализации, который описывает докладчик, практически идентичен тому, что предлагал Одерски больше года назад с проверяемыми исключениями (aka Capabilities).
В конце еще интересная мысль про то, что можно обрабатывать исключения и потом иметь возможность возвращаться к проблемному месту, но реализация этого явно дорога, а что она полезного даст — непонятно.
Устройство HTTP
Занятная статья, в первой части которой автор довольно просто рассказывает основные принципы работы HTTP/1.1 (я, например, никогда не задумывался о том, как работает отправка запроса/ответа частями (chunked)), а потом плавненько переходит на куски кода на Rust и подсвечивает кучу занятных вопросов про проксирование траффика HTTP/2 (в т.ч. для защиты от плохих парней). Поразительно, сколько есть серых зон в протоколе, и как хорошо, что в повседневной разработке о них не надо думать :)
Удаление дубликатов в таблице-списке
Способов удалить дубликаты в таблице довольно много: с использованием CTE, JOIN, подзапроса, извращений вроде пересоздания таблица через DISTINCT и т.д. Возьмем для примера группировку с USING:
DELETE FROM elements t1 USING elements t2 WHERE t1.some_key = t2.some_key AND t1.created_at > t2.created_at;
Тут одна колонка (some_key
) используется для собственно определения дубликатов, а вторая (created_at
) — для сортировки их между собой.
Но что делать если в таблице всего одна колонка, как различать два элемента, чтобы удалить не все? Тут пригодятся системные колонки. В данном случае — ctid
, “физическое расположение данной версии строки в таблице”:
DELETE FROM list t1 USING list t2 WHERE t1.some_key = t2.some_key AND t1.ctid > t2.ctid;
Получение Dockerfile по образу
Если кто-то один Dockerfile сломал, а другой потерял, то можно его почти полностью восстановить из готового образа:
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage yourimage:tag
Хуки СУБД и Transactional outbox
В событийно ориентированной архитектуре хуки довольно полезны: можно перед коммитом добавлять в транзакцию все события (transactional outbox, все дела), а после коммита — отправлять их в очередь сообщений. Однако стоит помнить, что количество соединений к базе ограничено и делать в них что-то длительное не стоит. И на эти грабли я наступил в процессе разработки, сделав отправку сообщений в RabbitMQ после коммита: соединения долго висели и в итоге кончались.
Один из вариантов решения — подцепиться к соединению и делать операции после его закрытия. Второй вариант — кидать задачку во внутреннюю очередь, и обрабатывать отдельным обработчиком. Первое может быть немного напряжно с точки зрения реализации (но в Exposed мне удалось это сделать). Я в итоге скомбинировал оба подхода. Стоит помнить, что гарантий, что что-то выполнится в хуке после транзакции/закрытия соединения нет (потому что приложение может упасть), поэтому должен быть страховочный механизм.
Уже потом нашел репозиторий-пост, в котором описан практически идентичный случай.
Перенос дашбордов Grafana
GitOps предполагает, что артефакты сначала создаются в репозитории, а потом синхронизируются с целью. Но в случае дашбордов это хоть и рабочий вариант, но очень болезненный: все-таки, большинство вещей “накликивается” через веб-морду, а не описываются через JSON (тем более, что в Grafana создать через UI одну панельку, а не весь дашборд, даже из готового JSON весьма нетривиально). При этом создание дашборда через CI позволяет его шаблонировать и относительно легко разворачивать на несколько сред (например, чтобы иметь одинаковый дашборд для stage и prod, если их дашборды в разных местах).
Однако шаблонировать можно и средствами самой Grafana. В дашборде можно создавать переменные (литералы, словари, мультивыбор), причем заполнять значения можно с помощью запроса. При этом переменные можно скрывать. Отдельно отмечу константные переменные — в них очень хорошо хранить то, что меняется между средами (например, namespace, домен и т.п.). Еще в переменные можно складывать частые фильтры и куски запросов, чтобы не копипастить их.
Если все обложить переменными, то тогда можно легко импортировать дашборд для внешнего использования. При импорте для другой Grafana будет автоматически предложено поменять все источники данных и, что самое важное, все константные переменные — так что в них можно забить все отличия, а в репозитории хранить только бэкапы. Таким образом можно и репе все хранить, и менять все более удобным способом.
Эволюция JS-фреймворков
Пост, в котором весьма сжато изложена эволюция web’а от статических страниц до-javascript’ной эпохи до современных MPA-фреймворков. Описаны проблемы, которые возникали в процессе развития этой экосистемы и подходы, которые применялись в фреймворках для их решения. Требует быть чуточку в теме, но в целом хорош для ответа на вопросы типа “чем отличается React от Vue” или “почему SPA уже не модно”.
Павлик internal
Продолжаю делать Павликов, потому что все нужное как всегда глубоко закопано и надо выбирать стул. На этот раз на очереди internal, который ограничивает видимость элемента модулем (т.е. если он в какой-то библиотеке, то только библиотека имеет к нему доступ). Его очень любят использовать сами Jetbrains в Ktor, корутинах, Exposed и т.п.
Но на самом деле это только ограничение компилятора самого Kotlin — на уровне java нет никакой видимости “на уровне модуля”, и можно этот internal
использовать как угодно. И на уровне Kotlin обойти его тоже элементарно: нужно всего лишь добавить @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
.
При этом многострадальный package-private, который сообщество хочет чуть ли не с релиза, в язык не добавляют. И очень смешно, что один из основных аргументов это “ну, все равно особо ни от чего не защищает, используйте internal
”.
В общем, типичная позиция JetBrains — если хочется, то совместимость с java — священная корова, а если очень надо — то и плевать на нее.
Поиск алгоритма умножения матриц нейронками
Год назад была работа про использование ML для сортировок. А на прошлой неделе — статья в Nature про использование нейронок для умножения матриц. Что интересно, для поиска оптимального алгоритма задачу представили в виде игры и переиспользовали наработки от AlphaZero (которая обыгрывала чемпионов мира по шахматам, сеги и го).
Как сказал один из моих студентов: “Не знал, что нейронные сети можно использовать для чего-то полезного” :)
Приборка подов в k8s
Поды, завершившиеся с ошибкой, могут висеть в кубере до посинения (тикет).
Чтобы почистить все руками, можно выполнить что-то вроде такого:
kubectl -n namespace delete pods --field-selector=status.phase=Failed
Хотя, конечно, лучше разобраться с первопричиной падения подов:)