Команда Go разрабатывает новый алгоритм сборки мусора — Green Tea 🍵, который радикально меняет подход к разметке памяти. Вместо обработки отдельных объектов он работает с целыми блоками по 8 КБ, но только для маленьких объектов (≤512 байт). Уже есть экспериментальная реализация, и в Go 1.25 её можно будет протестировать.
Сейчас Go использует классический алгоритм трёхцветной разметки, который:
- Не учитывает расположение объектов в памяти, сканирует указатели вразнобой
- Тратит 85% времени на цикл сканирования, из которых 35% CPU-циклов уходят просто на чтение памяти (из-за плохой локальности)
- Плохо масштабируется на системах с десятками ядер и сложной топологией памяти (NUMA, HBM)
Вместо работы с отдельными объектами, Green Tea оперирует целыми 8 КБ (для уменьшения промахов L1/L2) блоками называемыми интервалами, но только для маленьких объектов. Такой фокус на маленьких объектах обусловлен тем, что сборка их новым алгоритмом дает наибольший выигрыш:
- Дешевле сканировать блоками, чем по одному
- Они чаще создают фрагментацию и нагрузку на GC
Крупные объекты обрабатываются старым алгоритмом, чтобы не усложнять логику. Новый алгоритм Green Tea работает следующим образом:
- Каждый 8 КБ интервал хранит битовую маску (серый/чёрный биты) для своих объектов
- Если в блоке появляется новый указатель, он помечается серым, а весь блок ставится в очередь на сканирование
- Когда блок извлекается из очереди, все помеченные объекты в нём сканируются разом
Алгоритм базируется на гипотезе, что пока блок ждёт своей очереди, в нем накапливается все больше объектов на проверку. Поэтому когда очередь дойдет до блока, то будет проверено сильно больше одного объекта, что улучшает локальность.
Бенчи в целом хорошие:
- CPU-затраты GC снизились на 10–50% в CPU-bound сценариях
- В 2 раза меньше промахов кэша (L1/L2)
- Тесты компилятора Go показывают незначительный регресс (~0.5%)
Oригинал
Сейчас Go использует классический алгоритм трёхцветной разметки, который:
- Не учитывает расположение объектов в памяти, сканирует указатели вразнобой
- Тратит 85% времени на цикл сканирования, из которых 35% CPU-циклов уходят просто на чтение памяти (из-за плохой локальности)
- Плохо масштабируется на системах с десятками ядер и сложной топологией памяти (NUMA, HBM)
Вместо работы с отдельными объектами, Green Tea оперирует целыми 8 КБ (для уменьшения промахов L1/L2) блоками называемыми интервалами, но только для маленьких объектов. Такой фокус на маленьких объектах обусловлен тем, что сборка их новым алгоритмом дает наибольший выигрыш:
- Дешевле сканировать блоками, чем по одному
- Они чаще создают фрагментацию и нагрузку на GC
Крупные объекты обрабатываются старым алгоритмом, чтобы не усложнять логику. Новый алгоритм Green Tea работает следующим образом:
- Каждый 8 КБ интервал хранит битовую маску (серый/чёрный биты) для своих объектов
- Если в блоке появляется новый указатель, он помечается серым, а весь блок ставится в очередь на сканирование
- Когда блок извлекается из очереди, все помеченные объекты в нём сканируются разом
Алгоритм базируется на гипотезе, что пока блок ждёт своей очереди, в нем накапливается все больше объектов на проверку. Поэтому когда очередь дойдет до блока, то будет проверено сильно больше одного объекта, что улучшает локальность.
Бенчи в целом хорошие:
- CPU-затраты GC снизились на 10–50% в CPU-bound сценариях
- В 2 раза меньше промахов кэша (L1/L2)
- Тесты компилятора Go показывают незначительный регресс (~0.5%)
Oригинал
GitHub
runtime: green tea garbage collector · Issue #73581 · golang/go
Green Tea 🍵 Garbage Collector Authors: Michael Knyszek, Austin Clements Updated: 15 August 2025 This issue tracks the design and implementation of the Green Tea garbage collector. As of the last up...
❤3🔥2
На мой взгляд, одна из самых красивых идей в программировании — соответствие Карри-Ховарда. Это концепция, которая связывает программирование с логикой, показывая, что программы можно рассматривать как доказательства логических утверждений.
Обычно мы думаем о программах как о способах обработки данных: чисел, списков, функций. Но что, если взглянуть на это с другой стороны? Что, если программа — это доказательство того, что ее тип обитаем? То есть, если программа компилируется, она доказывает, что ее тип не пуст.
Например, рассмотрим простую функцию, которая создает пару:
Тип этой функции:
Теперь давайте переведем это на язык логики. Тип
Процесс вычисления программы можно рассматривать как упрощение доказательства. Например, рассмотрим функцию, которая меняет местами элементы пары:
Тип этой функции:
В логике это соответствует доказательству того, что если у нас есть доказательство для
Если тема показалось интересной, то в одном из блоков курса cs3110 ждут упражнения на OCaml 🐫
Обычно мы думаем о программах как о способах обработки данных: чисел, списков, функций. Но что, если взглянуть на это с другой стороны? Что, если программа — это доказательство того, что ее тип обитаем? То есть, если программа компилируется, она доказывает, что ее тип не пуст.
Например, рассмотрим простую функцию, которая создает пару:
let pair x y = (x, y)
Тип этой функции:
val pair : 'a -> 'b -> 'a * 'b
Теперь давайте переведем это на язык логики. Тип
'a * 'b соответствует логической формуле A /\ B, где /\ — это конъюнкция (логическое "и"). Таким образом, функция pair доказывает, что если у нас есть доказательства для A и B, мы можем создать доказательство для A /\ B.Процесс вычисления программы можно рассматривать как упрощение доказательства. Например, рассмотрим функцию, которая меняет местами элементы пары:
let swap (x, y) = (y, x)
Тип этой функции:
val swap : 'a * 'b -> 'b * 'a
В логике это соответствует доказательству того, что если у нас есть доказательство для
A /\ B, мы можем получить доказательство для B /\ A. Процесс вычисления функции swap можно рассматривать как упрощение этого доказательства.Если тема показалось интересной, то в одном из блоков курса cs3110 ждут упражнения на OCaml 🐫
✍3
Недавно я наткнулся на интересную поделку — Dockerfile Kotlin DSL. Да, вы не ослышались, это DSL на Kotlin для написания Dockerfile. Сомнительно, но представьте, как это удобно: вместо того чтобы писать Dockerfile в текстовом формате, вы можете использовать Kotlin для создания Dockerfile прямо в вашем Gradle-конфигурационном файле. Вот как выглядит простенький DSL для JVM-приложения:
Синтаксис выглядит очень похоже на привычный Dockerfile. Это удивительно, как можно минимизировать семантический разрыв между, казалось бы, совершенно разными языками.
Главная мотивация автора — избежать дублирования Dockerfile для разных сред (staging/production). Вместо того чтобы создавать отдельные Dockerfile для каждой среды, вы можете использовать один DSL-файл, который будет генерировать Dockerfile в зависимости от переменных окружения. Например, под разные окружения можно загрузить разные артефакты в образ:
Это может быть полезно, если вы не хотите засорять ваше приложение if-условиями и фича-флагами, и уже используете Gradle для сборки. Хотя на моей практике был всегда противоположный подоход через if-условия и фича-флаги.
Для меня, Dockerfile Kotlin DSL — это еще один пример того, насколько мощным может быть Kotlin для создания DSL. Он позволяет писать Dockerfile как код, что делает его более гибким и удобным для управления. И это не единственный пример. Есть ещё Exposed — ORM для Kotlin от JetBrains. В ней также удалось минимизировать семантический разрыв между DSL и SQL. Сравните SQL-запрос:
с его аналогом на Exposed DSL:
И это потрясающе. Восхищаюсь командой разработки языка, потому что до этого мир видел подобные DSL на динамически типизированных языках, как Ruby и Groovy. Но чтобы на статически типизированном и без необходимости получать PhD по теории категорий, это впервые 🔥
dockerfile {
from("openjdk:21-jdk-slim")
workdir("/app")
+"Копируем JAR-файл в Docker-образ"
copy {
source = "app.jar"
destination = "/app/app.jar"
}
cmd("java", "-jar", "app.jar")
}
Синтаксис выглядит очень похоже на привычный Dockerfile. Это удивительно, как можно минимизировать семантический разрыв между, казалось бы, совершенно разными языками.
Главная мотивация автора — избежать дублирования Dockerfile для разных сред (staging/production). Вместо того чтобы создавать отдельные Dockerfile для каждой среды, вы можете использовать один DSL-файл, который будет генерировать Dockerfile в зависимости от переменных окружения. Например, под разные окружения можно загрузить разные артефакты в образ:
+"Загружаем активы"
val assetPath =
when (System.getenv("MODE")) {
"local" -> "/first_100_assets.zip"
"staging" -> "/compressed_assets.zip"
else -> "/full_assets.zip"
}
Это может быть полезно, если вы не хотите засорять ваше приложение if-условиями и фича-флагами, и уже используете Gradle для сборки. Хотя на моей практике был всегда противоположный подоход через if-условия и фича-флаги.
Для меня, Dockerfile Kotlin DSL — это еще один пример того, насколько мощным может быть Kotlin для создания DSL. Он позволяет писать Dockerfile как код, что делает его более гибким и удобным для управления. И это не единственный пример. Есть ещё Exposed — ORM для Kotlin от JetBrains. В ней также удалось минимизировать семантический разрыв между DSL и SQL. Сравните SQL-запрос:
select * from user
where vibe = 'cheel'
and status = 'active'
с его аналогом на Exposed DSL:
User
.select { (User.vibe eq "cheel")
and (User.status eq "active") }
И это потрясающе. Восхищаюсь командой разработки языка, потому что до этого мир видел подобные DSL на динамически типизированных языках, как Ruby и Groovy. Но чтобы на статически типизированном и без необходимости получать PhD по теории категорий, это впервые 🔥
GitHub
GitHub - Dogacel/dockerfile-kotlin-dsl: Generate Dockerfiles using Kotlin DSL syntax
Generate Dockerfiles using Kotlin DSL syntax. Contribute to Dogacel/dockerfile-kotlin-dsl development by creating an account on GitHub.
🔥3👍1👎1💩1
Мимо меня чуть не прошла одна из главных новостей этого года — поддержка асинхронного ввода-вывода через io_uring в PostgreSQL 18. Напомню, что io_uring — это новая подсистема асинхронного ввода-вывода, разработанная для Linux, позволяющая асинхронно управлять операциями чтения-записи на диск. До недавнего времени операции ввода-вывода с файловой системой были синхронными и блокирующими.
Поддержка io_uring была реализована путем добавления нового уровня абстракции внутри ядра базы данных. Теперь система способна переключаться между традиционным синхронным вводом-выводом и новым асинхронным механизмом, выбирая оптимальный способ обработки запросов.
Основные компоненты PostgreSQL, затронутые изменениями:
- Sequential Scans: Последовательные сканирования таблиц теперь выполняются быстрее, поскольку данные считываются параллельно и обработка выполняется независимо друг от друга.
- Bitmap Heap Scans: Этот метод поиска данных стал более эффективным благодаря параллельному доступу к индексированным данным.
- Vacuum Operations: Очистка таблиц от устаревших записей также получила ускорение благодаря возможности параллельного исполнения операций удаления.
Эти улучшения были достигнуты именно за счёт внедрения асинхронного механизма, основанного на io_uring. Тестирование показало впечатляющие результаты: улучшение производительности вплоть до 2–3 раз 🔥
Главная причина прироста производительности заключается в устранении задержки при операциях ввода-вывода. Когда PostgreSQL отправляет запрос на чтение файла, процесс блокируется до завершения операции. Но с использованием io_uring база данных продолжает обрабатывать другие запросы, пока идёт операция ввода-вывода. Это значит, что даже самые тяжелые нагрузки на базу данных обрабатываются эффективнее.
Release notes
Поддержка io_uring была реализована путем добавления нового уровня абстракции внутри ядра базы данных. Теперь система способна переключаться между традиционным синхронным вводом-выводом и новым асинхронным механизмом, выбирая оптимальный способ обработки запросов.
Основные компоненты PostgreSQL, затронутые изменениями:
- Sequential Scans: Последовательные сканирования таблиц теперь выполняются быстрее, поскольку данные считываются параллельно и обработка выполняется независимо друг от друга.
- Bitmap Heap Scans: Этот метод поиска данных стал более эффективным благодаря параллельному доступу к индексированным данным.
- Vacuum Operations: Очистка таблиц от устаревших записей также получила ускорение благодаря возможности параллельного исполнения операций удаления.
Эти улучшения были достигнуты именно за счёт внедрения асинхронного механизма, основанного на io_uring. Тестирование показало впечатляющие результаты: улучшение производительности вплоть до 2–3 раз 🔥
Главная причина прироста производительности заключается в устранении задержки при операциях ввода-вывода. Когда PostgreSQL отправляет запрос на чтение файла, процесс блокируется до завершения операции. Но с использованием io_uring база данных продолжает обрабатывать другие запросы, пока идёт операция ввода-вывода. Это значит, что даже самые тяжелые нагрузки на базу данных обрабатываются эффективнее.
Release notes
PostgreSQL News
PostgreSQL 18 Beta 1 Released!
The PostgreSQL Global Development Group announces that the first beta release of PostgreSQL 18 is now [available for download](https://www.postgresql.org/download/). This …
🔥11
This media is not supported in your browser
VIEW IN TELEGRAM
На днях наткнулся на апрельский маркетинговый ролик от JetBrains, где собрали максимум хайпа: MCP, AI Coding Assistant, IoT приправленный Kotlin, только RAG не хватало. В видео автор вместе со зрителями пишет наык для Алисы MCP-сервер для управления лампочкой через LLM. Понятно, что ролик рекламный, но есть пара моментов, которые меня зацепили, о которых расскажу ниже.
Похоже, JetBrains первыми среди разработчиков IDE догадались сделать интерактивную работу ассистента: LLM сам пишет код, пытается его запустить, пока тесты не станут зелёными. Насладиться можно на отрывке видео.
Похожая фича появилась недавно у Jules от Google. Хотя такая работа ассистента кажется очевидной, реализовать её начали только сейчас — раньше разработчики самостоятельно создавали такие тулзы, как тут.
MCP только набирает популярность, а в JVM-экосистеме уже всё готово:
- Официальные SDK для MCP, в отличие от модных Rust и Go, для которых ещё нет SDK.
- Spring AI — для упрощения работы со всем циклом разработки LLM-приложений.
- Официальный плагин MCP в IntelliJ для обеспечения связи между LLM и средой разработки.
В целом здорово, что развивается вся экосистема, а разработчики языка не ограничиваются одним лишь туториалом, как их коллеги. Хотя в том же Go есть родная версия LangChain, похожего проекта для других языков кроме Python не встречал.
Похоже, JetBrains первыми среди разработчиков IDE догадались сделать интерактивную работу ассистента: LLM сам пишет код, пытается его запустить, пока тесты не станут зелёными. Насладиться можно на отрывке видео.
Похожая фича появилась недавно у Jules от Google. Хотя такая работа ассистента кажется очевидной, реализовать её начали только сейчас — раньше разработчики самостоятельно создавали такие тулзы, как тут.
MCP только набирает популярность, а в JVM-экосистеме уже всё готово:
- Официальные SDK для MCP, в отличие от модных Rust и Go, для которых ещё нет SDK.
- Spring AI — для упрощения работы со всем циклом разработки LLM-приложений.
- Официальный плагин MCP в IntelliJ для обеспечения связи между LLM и средой разработки.
В целом здорово, что развивается вся экосистема, а разработчики языка не ограничиваются одним лишь туториалом, как их коллеги. Хотя в том же Go есть родная версия LangChain, похожего проекта для других языков кроме Python не встречал.
👍4❤2
Чем больше я занимаюсь промт-инженерией, тем сильнее чувство, что я занимаюсь каким-то НЛП 🏌️
Но пока самый главный вывод: машина напишет для машины промт качественнее чем человек. Но как это сделать?
1.1 Берём какой-нибудь DeepSeek и описываем нашу проблему как человеку. Можно до этого воспользоваться техникой якорение из НЛП, сказав LLM, что она разработчик, например, в стартапе. Главное не жалеть токенов на первый промт.
1.2 Самое главное в конце первого промта попросить план решения проблемы. Спросить у модели, как она бы решала задачу.
2.1 Валидируем план от LLM, просим внести правки. Главная задача выстроить роад-мап нашей фичи верхнеуровнево.
2.2 Пользуемся техникой мета-модели из НЛП, делая фокус на первом этапе нашего плана. Просим модель написать план реализации первого этапа, валидируем при необходимости.
3. Просим написать промт по самому первому шагу нашего плана для Cursor.
Звучит бредово? Возможно, но зато работает. В комменте к посту приложил пример такого промта от LLM🍾
Но пока самый главный вывод: машина напишет для машины промт качественнее чем человек. Но как это сделать?
1.1 Берём какой-нибудь DeepSeek и описываем нашу проблему как человеку. Можно до этого воспользоваться техникой якорение из НЛП, сказав LLM, что она разработчик, например, в стартапе. Главное не жалеть токенов на первый промт.
1.2 Самое главное в конце первого промта попросить план решения проблемы. Спросить у модели, как она бы решала задачу.
2.1 Валидируем план от LLM, просим внести правки. Главная задача выстроить роад-мап нашей фичи верхнеуровнево.
2.2 Пользуемся техникой мета-модели из НЛП, делая фокус на первом этапе нашего плана. Просим модель написать план реализации первого этапа, валидируем при необходимости.
3. Просим написать промт по самому первому шагу нашего плана для Cursor.
Звучит бредово? Возможно, но зато работает. В комменте к посту приложил пример такого промта от LLM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3
Глубинный котер
Чем больше я занимаюсь промт-инженерией, тем сильнее чувство, что я занимаюсь каким-то НЛП 🏌️ Но пока самый главный вывод: машина напишет для машины промт качественнее чем человек. Но как это сделать? 1.1 Берём какой-нибудь DeepSeek и описываем нашу проблему…
This media is not supported in your browser
VIEW IN TELEGRAM
В предыдущем посте я рассказал о продвинутых приёмах промт-инженерии, которые применил в своём новом проекте ast2llm-go — инструменте, который делает общение с AI-ассистентами умнее и эффективнее. Какую проблему я пытаюсь решить?
Часто случалось, что при запросе к Copilot/Cursor забываешь передать контекст проекта, и в итоге получаешь:
- Несуществующие методы
- Ошибки в сигнатурах
- Бесконечные «Уточните структуру проекта...»
Это раздражало, особенно в enterprise-проектах, где кодовая база большая, и для добавления фичи приходится затрагивать кучу файлов. Решение напрашивалось само — автоматически собирать контекст из исходников.
Идея пришла как раз во время хайпа вокруг MCP-серверов, которые позволяют LLM самостоятельно обогащать свой контекст. Так я реализовал MCP-сервер для анализа Go-проектов через AST. Вот как это работает:
1. Модель понимает, что ей не хватает информации о какой-то структуре, используемой в коде.
2. LLM запрашивает у MCP-сервера подробности об импортируемых функциях, структурах и т. д.
3. MCP-сервер парсит проект, проходит по AST и собирает все неизвестные для LLM элементы в текстовый ответ.
4. Модель получает данные и меньше галлюцинирует.
В гифке к посту показан простой пример использования этого MCP-сервера в Cursor. В итоге LLM получает ответ вроде такого и сразу пишет правильный код:
Пока проект в пилоте — я его обкатываю и не уверен на 100% в выбранном подходе. В его защиту могу сказать, что тот же repomix работает довольно топорно: он просто вставляет весь проект в контекст LLM, что не всегда возможно, да и замусоренный контекст плохо себя показывает на практике. В Cursor есть встроенный grep, и агент может искать нужный код, но это происходит медленно, да и сам редактор по понятным причинам не всегда подходит для enterprise.
Почему Go? Потому что у него простой синтаксис, и такой инструмент было проще всего реализовать. Плюс это мой основной рабочий язык сейчас. В планах — отдельные MCP-серверы для других экосистем, так что если интересно, присоединяйтесь к ast2llm, пишите в личку. Будем вместе двигать технологии обогащения контекста для LLM🍪
Часто случалось, что при запросе к Copilot/Cursor забываешь передать контекст проекта, и в итоге получаешь:
- Несуществующие методы
- Ошибки в сигнатурах
- Бесконечные «Уточните структуру проекта...»
Это раздражало, особенно в enterprise-проектах, где кодовая база большая, и для добавления фичи приходится затрагивать кучу файлов. Решение напрашивалось само — автоматически собирать контекст из исходников.
Идея пришла как раз во время хайпа вокруг MCP-серверов, которые позволяют LLM самостоятельно обогащать свой контекст. Так я реализовал MCP-сервер для анализа Go-проектов через AST. Вот как это работает:
1. Модель понимает, что ей не хватает информации о какой-то структуре, используемой в коде.
2. LLM запрашивает у MCP-сервера подробности об импортируемых функциях, структурах и т. д.
3. MCP-сервер парсит проект, проходит по AST и собирает все неизвестные для LLM элементы в текстовый ответ.
4. Модель получает данные и меньше галлюцинирует.
В гифке к посту показан простой пример использования этого MCP-сервера в Cursor. В итоге LLM получает ответ вроде такого и сразу пишет правильный код:
Used Items From Other Packages:
Struct: testme/dto.MyDTO
Fields:
- Foo string
- Bar int
Пока проект в пилоте — я его обкатываю и не уверен на 100% в выбранном подходе. В его защиту могу сказать, что тот же repomix работает довольно топорно: он просто вставляет весь проект в контекст LLM, что не всегда возможно, да и замусоренный контекст плохо себя показывает на практике. В Cursor есть встроенный grep, и агент может искать нужный код, но это происходит медленно, да и сам редактор по понятным причинам не всегда подходит для enterprise.
Почему Go? Потому что у него простой синтаксис, и такой инструмент было проще всего реализовать. Плюс это мой основной рабочий язык сейчас. В планах — отдельные MCP-серверы для других экосистем, так что если интересно, присоединяйтесь к ast2llm, пишите в личку. Будем вместе двигать технологии обогащения контекста для LLM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5❤2
TL;DR: ФП продолжает идти в массы, а Kotlin становится все лучше.
Посмотрел доклад с Kotlin Conf 2025 о новой фиче — Rich Errors — которая сделает обработку ошибок удобной, без зоопарка комбинаторов или нелинейной логики с исключениями. Если фичу выкатят, Kotlin станет первым популярным языком послеGo Rust, где ошибки как значения встроены на уровне синтаксиса.
Сейчас в Kotlin есть несколько способов работать с ошибками:
1. Исключения — но они не типобезопасны и неявны
2. Nullable-типы (
3.
Вот типичный пример с
Проблема в том, что:
- Много boilerplate — постоянно приходится вызывать
- Нет exhaustiveness checking — если добавим новый тип ошибки, компилятор не подскажет, что её нужно обработать.
- Неудобные цепочки вызовов — в отличие от nullable-типов, где есть
С новыми rich error теперь можно помечать классы как error, и они автоматически становятся частью системы типов:
Функция может явно указать, какие ошибки она возвращает:
Теперь можно использовать
Можно писать цепочки, как с nullable:
В Scala проблему с комбинаторами для удобной работы с ошибками решают с помощью Tagless Final — подход, где ошибки явно описаны в типах, а комбинаторы скрыты внутри алгебры. Kotlin будет делать что-то похожее, но на уровне языка.
Обещают KEEP по фиче этим летом и добавления в Koltin 2.4. Ждёмс🙈
Посмотрел доклад с Kotlin Conf 2025 о новой фиче — Rich Errors — которая сделает обработку ошибок удобной, без зоопарка комбинаторов или нелинейной логики с исключениями. Если фичу выкатят, Kotlin станет первым популярным языком после
Сейчас в Kotlin есть несколько способов работать с ошибками:
1. Исключения — но они не типобезопасны и неявны
2. Nullable-типы (
T?) — простые, но не дают информации о причине ошибки 3.
Result<T> / Either<L, R> — типобезопасно, но требует fold, mapLeft и прочих комбинаторовВот типичный пример с
Result:
fun getUser(): Result<User> {
val user = fetchUser().getOrElse { return Result.failure(it) }
val parsedUser = user.parseUser().getOrElse { return Result.failure(it) }
return Result.success(parsedUser)
}
fun usage() {
getUser()
.onSuccess { user -> println(user.name) }
.onFailure { error ->
when (error) {
is NetworkException -> println("Failed to fetch user")
is ParsingException -> println("Failed to parse user")
else -> println("Unhandled error")
}
}
}
Проблема в том, что:
- Много boilerplate — постоянно приходится вызывать
getOrElse, onSuccess, onFailure. - Нет exhaustiveness checking — если добавим новый тип ошибки, компилятор не подскажет, что её нужно обработать.
- Неудобные цепочки вызовов — в отличие от nullable-типов, где есть
?., тут приходится вручную разворачивать Result. С новыми rich error теперь можно помечать классы как error, и они автоматически становятся частью системы типов:
error class NetworkError(val code: Int)
Функция может явно указать, какие ошибки она возвращает:
fun fetchUser(): User | NetworkError | UserNotFoundError {
...
}
Теперь можно использовать
when с exhaustive checking (компилятор проверит, что все случаи обработаны):
fun loadUserData() {
val result = fetchUser()
when (result) {
is User -> show(result)
is NetworkError -> showError("Network issue (${result.code}). Try again.")
is UserNotFoundError -> showError("User not found. Check your credentials.")
// Компилятор заставит обработать все варианты!
}
}
Можно писать цепочки, как с nullable:
fun getUser(): User | NetworkError | ParsingError {
return fetchUser() // User | NetworkError
?.parseUser() // User | ParsingError
}
В Scala проблему с комбинаторами для удобной работы с ошибками решают с помощью Tagless Final — подход, где ошибки явно описаны в типах, а комбинаторы скрыты внутри алгебры. Kotlin будет делать что-то похожее, но на уровне языка.
Обещают KEEP по фиче этим летом и добавления в Koltin 2.4. Ждёмс
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
В правилах для код-агента от Его Бугаенко наткнулся на интересную строчку:
Мне, как человеку, выросшему на pytest с его удобными фикстурами, такое ограничение (отсутствие подготовки/очистки данных) показалось странным. Однако, пообщавшись с более опытными коллегами, я узнал, что существует альтернативная точка зрения.
Некоторые разработчики выступают против явной подготовки и очистки данных в тестах, аргументируя это тем, что тестируемые компоненты должны работать в условиях, максимально приближенных к реальным. В такой парадигме тесты выполняются без изоляции, что помогает выявлять проблемы, связанные с конкурентным доступом, состоянием системы или внешними зависимостями.
Теперь отсуствие такой искуственной изоляции на текущем проекте выглядит оправдано🙂
Tests may not use setUp() or tearDown() idioms.
Мне, как человеку, выросшему на pytest с его удобными фикстурами, такое ограничение (отсутствие подготовки/очистки данных) показалось странным. Однако, пообщавшись с более опытными коллегами, я узнал, что существует альтернативная точка зрения.
Некоторые разработчики выступают против явной подготовки и очистки данных в тестах, аргументируя это тем, что тестируемые компоненты должны работать в условиях, максимально приближенных к реальным. В такой парадигме тесты выполняются без изоляции, что помогает выявлять проблемы, связанные с конкурентным доступом, состоянием системы или внешними зависимостями.
Теперь отсуствие такой искуственной изоляции на текущем проекте выглядит оправдано
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - yegor256/prompt: A plain-text prompt for LLMs that teaches the essence of elegant coding and testing—save it to ~/.claude/CLAUDE.md.
A plain-text prompt for LLMs that teaches the essence of elegant coding and testing—save it to ~/.claude/CLAUDE.md. - yegor256/prompt
❤2😁1🤔1
Разбирая реализации MCP-серверов в разных экосистемах, я был впечатлён разницей в объёме кода при схожих характеристиках (статическая типизация, асинхронность, GC). Go-версия занимает 400+ строк, тогда как Kotlin-решение укладывается в 100 строк— и всё благодаря выразительности языка и богатой стандартной библиотеке Kotlin.
В Go-реализации все механизмы синхронизации прописаны явно:
Каждое изменение shared-состояния требует явной блокировки:
В Kotlin аналогичная функциональность достигается проще:
Доступ к ConcurrentHashMap автоматически потокобезопасен:
Запуск асинхронной задачи в Go с коммуникацией через канал:
Эквивалентная логика в Kotlin более лаконична:
Исходники:
- на Kotlin
- на Go
В Go-реализации все механизмы синхронизации прописаны явно:
type stdioSession struct {
pendingRequests map[int64]chan *samplingResponse // Хэш-таблица запросов
pendingMu sync.RWMutex // Мьютекс для доступа
requestID atomic.Int64 // Атомарный счётчик
}
Каждое изменение shared-состояния требует явной блокировки:
s.pendingMu.Lock()
s.pendingRequests[id] = responseChan
s.pendingMu.Unlock()
В Kotlin аналогичная функциональность достигается проще:
private val pendingRequests = ConcurrentHashMap<Long, Channel<SamplingResponse>>()
private val requestId = AtomicLong(0)
Доступ к ConcurrentHashMap автоматически потокобезопасен:
pendingRequests[id] = responseChannel // Не нужны явные блокировки
Запуск асинхронной задачи в Go с коммуникацией через канал:
go func() {
response := s.server.HandleMessage(ctx, rawMessage)
if err := s.writeResponse(response, writer); err != nil {
s.errLogger.Printf("Error: %v", err)
}
}()
Эквивалентная логика в Kotlin более лаконична:
scope.launch {
val response = server.handleMessage(rawMessage)
writeChannel.send(response) // Потокобезопасная отправка
}
Исходники:
- на Kotlin
- на Go
✍4🤔2
После coq продолжаю знакомиться с чудесами Computer Science. На этот раз познакомился со статьей Checking Polynomial Time Complexity with Types за авторством Patrick Baillot, которая посвящена верификации полиномиальной временной сложности программ с помощью систем типов.
В статье используется модифицированная система типов на основе Light Affine Logic, где модальности ограничивают дублирование данных и глубину рекурсии. Это гарантирует, что типизированные программы выполняются за полиномиальное время.
Примеры модельностей:
-
-
Например, рекурсивные функции проверяются на соответствие линейным и стратифицированным правилам, исключая неконтролируемый рост вычислений. Тип
В коде такой подход можно проиллюстрировать так:
Здесь модальность
В статье используется модифицированная система типов на основе Light Affine Logic, где модальности ограничивают дублирование данных и глубину рекурсии. Это гарантирует, что типизированные программы выполняются за полиномиальное время.
Примеры модельностей:
-
! (экспоненциал) контролирует, сколько раз значение может быть использовано.-
§ (мягкий экспоненциал) ограничивает глубину рекурсивных вызовов.Например, рекурсивные функции проверяются на соответствие линейным и стратифицированным правилам, исключая неконтролируемый рост вычислений. Тип
N → §N для функции означает, что каждый рекурсивный вызов уменьшает «глубину» страта, обеспечивая полиномиальную остановку.В коде такой подход можно проиллюстрировать так:
-- Тип с модальностью §, ограничивающей рекурсию
data § a = § a
-- Функция сложения с контролем глубины
add :: §Int -> §Int -> §Int
add (§x) (§y) = §(x + y)
-- Рекурсивная функция с ограничением через типы
factorial :: Int -> §Int
factorial 0 = §1
factorial n = §(n * un§ (factorial (n-1))) -- un§ "снимает" модальность
Здесь модальность
§ гарантирует, что глубина рекурсии factorial ограничена, а время выполнения остается полиномиальным.❤2
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5❤1
Представьте ситуацию: вы пишете на языке, где нельзя объявлять функции с именами (чистое лямбда-исчисление), или работаете с ограничениями вроде тех, что есть в Haskell. Как сделать рекурсивную функцию?
Обычный факториал выглядит просто:
Но что если мы не можем ссылаться на
1. Мы передаём функцию в саму себя как аргумент
2. Y-комбинатор организует "бесконечную" подачу функции в саму себя
3. Рекурсия работает, хотя явного вызова по имени нет
Вот как я реализовал это в Kotlin (люблю его за выразительность):
Ключевые идеи реализации:
1.
2. Он подсовывает ей саму себя в качестве аргумента
3. Когда функция вызывает
Для меня рекурсия всегда была неотъемлемой частью языка программирования — чем-то данным "из коробки". Но Y-комбинатор показал, что рекурсию можно собрать буквально из ничего, используя только композицию термов даже в простейшем лямбда-исчислении.
Это как обнаружить, что ваш любимый конструктор Lego на самом деле состоит из ещё более фундаментальных деталей😳
Обычный факториал выглядит просто:
fun factorial(n: Int): Int = if (n <= 1) 1 else n * factorial(n - 1)
Но что если мы не можем ссылаться на
factorial внутри его же определения? Вот тут-то и нужен Y-комбинатор, который позволяет функции вызывать саму себя, не имея имени:1. Мы передаём функцию в саму себя как аргумент
2. Y-комбинатор организует "бесконечную" подачу функции в саму себя
3. Рекурсия работает, хотя явного вызова по имени нет
Вот как я реализовал это в Kotlin (люблю его за выразительность):
typealias Func<A> = (A) -> A
fun <A> yCombinator(f: (Func<A>) -> Func<A>): Func<A> {
return { x -> f(yCombinator(f))(x) } // Магия!
}
val factorial: Func<Int> = yCombinator { recurse -> {
n -> if (n <= 1) 1 else n * recurse(n - 1)
} // Лямбда внутри лямбды
}
println(factorial(5)) // 120
Ключевые идеи реализации:
1.
yCombinator принимает функцию, которая хочет быть рекурсивной2. Он подсовывает ей саму себя в качестве аргумента
recurse 3. Когда функция вызывает
recurse — она неявно вызывает саму себя Для меня рекурсия всегда была неотъемлемой частью языка программирования — чем-то данным "из коробки". Но Y-комбинатор показал, что рекурсию можно собрать буквально из ничего, используя только композицию термов даже в простейшем лямбда-исчислении.
Это как обнаружить, что ваш любимый конструктор Lego на самом деле состоит из ещё более фундаментальных деталей
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔6❤2👀1
На недавнем DDD Europe выступил с докладом Gregor Hohpe — автор книги Enterprise Integration Patterns (да, той самой!). Доклад посвящен инженерии платформ, и красной нитью в нём проходит мысль simple ≠ easy. Ниже приведены основные идеи доклада.
Цель платформы заключается в расширении игрового поля и стимуляции инноваций. Часто внутренние проекты терпят крах, поскольку продуктовые разработчики считают, что действуют верно, тогда как команда платформы пытается ограничивать их возможности вместо предоставления набора необходимых инструментов. Возможность пользователям самостоятельно разрабатывать новые функции является признаком успешности платформы.
Автомобильная индустрия активно применяет платформы, позволяя создавать широкий спектр моделей автомобилей. Благодаря экономии на масштабах производства, возможно внедрение сложных технологий для различных рыночных сегментов. На единой платформе создаются многочисленные вариации транспортных средств.
Ошибка Cadillac Cimarron: Компания попыталась выпустить новый автомобиль на базе существующего, лишь дополнив его несколькими функциями. Однако игнорировала необходимость построения полноценной платформы. Результат оказался провальным, так как машина не обладала уникальными характеристиками, выделявшими её среди конкурентов.
Команда платформы не должна вмешиваться непосредственно в цикл разработки. Задача платформы состоит в обеспечении условий для самостоятельной работы разработчиков и минимизации трения. Аналогично железнодорожным путям, обеспечивающим плавное движение поездов и автоматическое центрирование вагонов. Ограничители необходимы исключительно в экстренных ситуациях.
Названия элементов должны передавать суть, а не конкретные детали реализации. Название "автомобиль" удачно описывает сущность транспортного средства ("движется самостоятельно"), в отличие от названия "педаль газа", которое привязано к конкретной технической особенности двигателя внутреннего сгорания и несправедливо для электромобилей. Оптимальное наименование — "педаль ускорения".
Абстракция важна для правильного понимания системы. Понимание по умолчанию легко вводить в заблуждение, создавая ложное впечатление простоты, хотя в реальности задача становится сложнее. Простота интерфейса может маскировать важную информацию и увеличивать когнитивную нагрузку. Уменьшение количества параметров не обязательно снижает сложность восприятия.
Абстрагирование может приводить к ошибочному восприятию реальной проблемы. Основная цель абстракций — выражение сложности, а не упрощение. Ориентация на специфику предметной области делает систему интуитивно понятной. Разработчики должны предлагать уникальные сервисы, основываясь на своей экспертизе в предметной области.
Цель платформы заключается в расширении игрового поля и стимуляции инноваций. Часто внутренние проекты терпят крах, поскольку продуктовые разработчики считают, что действуют верно, тогда как команда платформы пытается ограничивать их возможности вместо предоставления набора необходимых инструментов. Возможность пользователям самостоятельно разрабатывать новые функции является признаком успешности платформы.
Автомобильная индустрия активно применяет платформы, позволяя создавать широкий спектр моделей автомобилей. Благодаря экономии на масштабах производства, возможно внедрение сложных технологий для различных рыночных сегментов. На единой платформе создаются многочисленные вариации транспортных средств.
Ошибка Cadillac Cimarron: Компания попыталась выпустить новый автомобиль на базе существующего, лишь дополнив его несколькими функциями. Однако игнорировала необходимость построения полноценной платформы. Результат оказался провальным, так как машина не обладала уникальными характеристиками, выделявшими её среди конкурентов.
Команда платформы не должна вмешиваться непосредственно в цикл разработки. Задача платформы состоит в обеспечении условий для самостоятельной работы разработчиков и минимизации трения. Аналогично железнодорожным путям, обеспечивающим плавное движение поездов и автоматическое центрирование вагонов. Ограничители необходимы исключительно в экстренных ситуациях.
Названия элементов должны передавать суть, а не конкретные детали реализации. Название "автомобиль" удачно описывает сущность транспортного средства ("движется самостоятельно"), в отличие от названия "педаль газа", которое привязано к конкретной технической особенности двигателя внутреннего сгорания и несправедливо для электромобилей. Оптимальное наименование — "педаль ускорения".
Абстракция важна для правильного понимания системы. Понимание по умолчанию легко вводить в заблуждение, создавая ложное впечатление простоты, хотя в реальности задача становится сложнее. Простота интерфейса может маскировать важную информацию и увеличивать когнитивную нагрузку. Уменьшение количества параметров не обязательно снижает сложность восприятия.
Абстрагирование может приводить к ошибочному восприятию реальной проблемы. Основная цель абстракций — выражение сложности, а не упрощение. Ориентация на специфику предметной области делает систему интуитивно понятной. Разработчики должны предлагать уникальные сервисы, основываясь на своей экспертизе в предметной области.
YouTube
Platform Engineering is Domain Driven Design - Gregor Hohpe - DDD Europe 2025
Domain-Driven Design Europe 2025 - Organised by Aardling (https://aardling.eu/)
https://dddeurope.com
https://newsletter.dddeurope.com/
https://be.linkedin.com/company/domain-driven-design-europe
https://bsky.app/profile/dddeu.bsky.social
https://masto…
https://dddeurope.com
https://newsletter.dddeurope.com/
https://be.linkedin.com/company/domain-driven-design-europe
https://bsky.app/profile/dddeu.bsky.social
https://masto…
❤4
В то время как в компаниях уже на проде используют Kotlin MP, Apple недавно выложили Swift SDK под Android — можно писать нативное приложение сразу под две платформы.
Похоже, что идея KMP стрельнула и для улучшения конкурентоспособности экосистемы Swift слепили SDK под Android. Из интересного, что используется интероп Swift-Java (третий по счёту в Swift?)
Причем у Swift есть все шансы потеснить Kotlin в этой нише хотя бы за счет легкости DX и более дружелюбного синтаксиса.
С другой стороны, на Kotlin много бэкенда, да и KMP на проде уже крутится.
Наблюдаем за этой гонкой🍿
Похоже, что идея KMP стрельнула и для улучшения конкурентоспособности экосистемы Swift слепили SDK под Android. Из интересного, что используется интероп Swift-Java (третий по счёту в Swift?)
Причем у Swift есть все шансы потеснить Kotlin в этой нише хотя бы за счет легкости DX и более дружелюбного синтаксиса.
С другой стороны, на Kotlin много бэкенда, да и KMP на проде уже крутится.
Наблюдаем за этой гонкой
Please open Telegram to view this post
VIEW IN TELEGRAM
Swift.org
Announcing the Swift SDK for Android
Swift has matured significantly over the past decade — extending from cloud services to Windows applications, browser apps, and microcontrollers. Swift powers apps and services of all kinds, and thanks to its great interoperability, you can share code across…
👍4
Because request parameters and responses are handled inside route lambdas, the plugin cannot infer detailed request/response schemas automatically. To generate a complete and useful specification, you can use annotations.
Cтыдно, очень стыдно
Ktor Help
OpenAPI specification generation | Ktor
🌚4
Антропик посягнул на святое 🤱
Буквально сегодня выложили ролик с демонстрацией того, как можно использовать Клод для модернизации старого доброго кода на Коболе. В качестве примера они взяли репозиторий AWS Mainframe Modernization (!!!) — там есть система управления кредитными картами.
А я уже хотел купить курс по Коболу и попасть в золотую КОБОЛу🙈
Буквально сегодня выложили ролик с демонстрацией того, как можно использовать Клод для модернизации старого доброго кода на Коболе. В качестве примера они взяли репозиторий AWS Mainframe Modernization (!!!) — там есть система управления кредитными картами.
А я уже хотел купить курс по Коболу и попасть в золотую КОБОЛу
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Claude Code modernizes a legacy COBOL codebase
Watch how Claude Code helps modernize a mainframe codebase.
Starting with code from an AWS Mainframe Modernization demo environment, Claude Code analyzes business logic, dependencies, and data flows, then helps refactor it into Java while preserving functionality.…
Starting with code from an AWS Mainframe Modernization demo environment, Claude Code analyzes business logic, dependencies, and data flows, then helps refactor it into Java while preserving functionality.…
❤4🤣1