В экосистеме Cats Effect есть утилита Hotswap, предназначенная для управления жизненным циклом заменяемых "на горячую" ресурсов через общий ресурсный скоуп.
К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от конкурентных попыток свопа, а релиз заменяемого ресурса не отслеживает его использование другими файберами, что легко приводит к утечке.
Для trace4cats понадобилась подобная штука, и мы с Chris Jansen сделали конкурентную обёртку над
При этом:
- конкурентные свопы (
- своп не блокируется конкурентными чтениями во время аллокации нового ресурса: как только новый ресурс создан, он может быть прочитан из других файберов;
- конкурентные доступы к ресурсу не блокируют друг друга и практически никогда не блокируется свопом, но всё же есть небольшой шанс обратиться к ресурсу именно в момент перед перезаписью ссылки на него, но не успеть захватить блокировку до того, как это сделает финалайзер в свопе.
* здесь и далее имеется в виду семантическая блокировка
Запаблишили пока отдельной либой под Scala 2.12, 2.13 и 3, правда только для CE3 🤷🏻♂️ Может быть в будущем оно переедет в
Какие уроки я вынес для себя из этой задачки:
- сложно понять cancelation и корректно учесть в коде все ситауции с отменой;
- написать такой же хороший код для CE2 сложно: там аллокация ресурса в принципе неотменяема, поэтому нельзя использовать
- написать вообще хороший конкарренси код с первого раза невозможно: мы написали с пятого — и то между 2-й и 3-й попытками прошло много времени, прежде чем мы обнаружили проблему.
Ещё из приятного: свой первый кросс-релиз под Scala 3 сделал за несколько минут.
К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от конкурентных попыток свопа, а релиз заменяемого ресурса не отслеживает его использование другими файберами, что легко приводит к утечке.
Для trace4cats понадобилась подобная штука, и мы с Chris Jansen сделали конкурентную обёртку над
Hotswap, лишённую перечисленных недостатков.При этом:
- конкурентные свопы (
swap) блокируют* друг друга, защищая внутренний Hotswap;
- каждый доступ (access) к текущей версии ресурса контролируется отдельным ресурсным скоупом: если исполнение хотя бы одного файбера находится внутри такого скоупа, ресурс гарантированно не будет финализирован при свопе — своп заблокируется, пока ресурс не будет освобожден всеми пользователями;- своп не блокируется конкурентными чтениями во время аллокации нового ресурса: как только новый ресурс создан, он может быть прочитан из других файберов;
- конкурентные доступы к ресурсу не блокируют друг друга и практически никогда не блокируется свопом, но всё же есть небольшой шанс обратиться к ресурсу именно в момент перед перезаписью ссылки на него, но не успеть захватить блокировку до того, как это сделает финалайзер в свопе.
* здесь и далее имеется в виду семантическая блокировка
Запаблишили пока отдельной либой под Scala 2.12, 2.13 и 3, правда только для CE3 🤷🏻♂️ Может быть в будущем оно переедет в
cats.effect.std. Критика Hotswap отражена в этом issue.Какие уроки я вынес для себя из этой задачки:
- сложно понять cancelation и корректно учесть в коде все ситауции с отменой;
- написать такой же хороший код для CE2 сложно: там аллокация ресурса в принципе неотменяема, поэтому нельзя использовать
Resource для содания и композиции отменяемых скоупов, а в CE3 есть Poll (см. разницу между withPermit в CE2 и permit в CE3 на Semaphore);- написать вообще хороший конкарренси код с первого раза невозможно: мы написали с пятого — и то между 2-й и 3-й попытками прошло много времени, прежде чем мы обнаружили проблему.
Ещё из приятного: свой первый кросс-релиз под Scala 3 сделал за несколько минут.
До сих пор считаю doobie самой хорошей библиотекой для работы с JDBC, при всех минусах самого протокола. Правда, из её дизайна следует ряд проблем, из-за которых с её помощью довольно сложно сразу начать писать правильный tagless final код для транзакционной бизнес-логики.
Во-первых, для описания действий с базой используется Free-монада
Во-вторых, достаточно полезная трейсинговая информация о выполнении SQL-запросов может быть передана только в
В-третьих, правильно дополнить транзакционную логику какими-нибудь посторонними (пусть и неоткатываемыми) эффектами в CE2 было непросто, а в CE3 стало очень сложно (возможно, напишу об этом отдельно).
В-четвертых, писать код с конкретной монадой — не наш путь. Особенно, если при попытке решить часть предыдущих проблем вместо
Хочется простого — работать с абстрактным DB-эффектом с поддержкой контекстного логирования и трассировки, ограничивая его способности только необходимыми баундами и алгебрами с бизнес-логикой. Главный вопрос — как?
Во-первых, для описания действий с базой используется Free-монада
ConnectionIO, умеющая довольно многое (весь набор JDBC-операций + инстансы сильных тайпклассов из CE), но не умеющая таскать произвольный контекст, как это умеют ZIO, Env или ReaderT. Кроме того, ConnectionIO вшита в Transactor, выполняющий её интерпретацию в произвольный эффект и управляющий транзакцией.Во-вторых, достаточно полезная трейсинговая информация о выполнении SQL-запросов может быть передана только в
LogHandler - сайд-эффектящий обработчик вида LogEvent => Unit, что при использовании "в лоб" затрудняет идентификацию этих событий в логах.В-третьих, правильно дополнить транзакционную логику какими-нибудь посторонними (пусть и неоткатываемыми) эффектами в CE2 было непросто, а в CE3 стало очень сложно (возможно, напишу об этом отдельно).
В-четвертых, писать код с конкретной монадой — не наш путь. Особенно, если при попытке решить часть предыдущих проблем вместо
ConnectionIO приходится использовать что-то ещё более сложное.Хочется простого — работать с абстрактным DB-эффектом с поддержкой контекстного логирования и трассировки, ограничивая его способности только необходимыми баундами и алгебрами с бизнес-логикой. Главный вопрос — как?
Для решения проблем из предыдущего поста я пробовал разные подходы в течение нескольких лет, но в начале прошлого года мои идеи окончательно кристаллизовались и попали в
Итак, решение было таким:
1. Ввести абстрактный DB-эффект.
2. Создать фасад над транзактором, поддерживающий работу с произвольным DB-эффектом.
3. Ввести тайпклассы для лифтинга в DB-эффект операций с БД (описываемых в
4. Использовать выше обозначенные сущности для описания транзакционной логики в TF-стиле.
5. Выбрать достаточно мощную монаду — замену для
6. Научить
С пп. 1–4 было всё понятно, сложность представляли остальные два пункта.
На выбор новой DB-монады меня подтолкнуло то, что
С этим решением в прод поехали несколько моих сервисов и проекты других компаний, где используют
Сюрприз прилетел весной этого года, когда я начал наконец погружаться в CE3 и соответствующие изменения в
Даже не помню, как я к этому пришёл, но решил рассмотреть континуальное представление
Больше всего я опасался невозможности выразить транзактор с сохранением API и поддержкой стримов. Но к счастью,
Всё получилось даже на CE2. Жаль, я не додумался до континуального представления раньше. Спасибо дедушке Йонеде за нашу прекрасную молодость.
Отправил сегодня PR в
Кстати, по миграции на CE3 стало заметно, как Rob Norris забросил развитие
tofu.Итак, решение было таким:
1. Ввести абстрактный DB-эффект.
2. Создать фасад над транзактором, поддерживающий работу с произвольным DB-эффектом.
3. Ввести тайпклассы для лифтинга в DB-эффект операций с БД (описываемых в
ConnectionIO) и дополнительных нетранзакционных действий (описываемых в F). 4. Использовать выше обозначенные сущности для описания транзакционной логики в TF-стиле.
5. Выбрать достаточно мощную монаду — замену для
ConnectionIO, для которой можно было бы вывести все нужные инстансы и реализовать соответствующие лифтинги и транзактор с поддержкой типизированного контекста.6. Научить
LogHandler работать с контекстным логером. С пп. 1–4 было всё понятно, сложность представляли остальные два пункта.
На выбор новой DB-монады меня подтолкнуло то, что
ConnectionIO в CE2 имела инстанс LiftIO, а сама IO тогда была неким универсальным способом интеропа между любыми библиотеками эффектов. Казалось, что ConnectionRIO[R, *] (обычный ReaderT поверх ConnectionIO) решает все проблемы: контекстные эффекты легко лифтятся в неё через ряд преобразований, написать аналог Transactor, умеющий интерпретировать ConnectionRIO в такой же контекстный эффект, тоже не очень сложно. Для LogHandler была написана специальная обёртка, позволяющая через UnliftIO и Embed встроить сайд-эффектящий, но не теряющий контекст хэндлер в любой DB-модуль.С этим решением в прод поехали несколько моих сервисов и проекты других компаний, где используют
tofu.Сюрприз прилетел весной этого года, когда я начал наконец погружаться в CE3 и соответствующие изменения в
doobie. Оказалось, что из-за общего редизайна залифтить в ConnectionIO стало возможно только Future — а получить её в чистом виде, вне контекста F, достаточно проблематично. Пришлось искать другой способ выражения DB.Даже не помню, как я к этому пришёл, но решил рассмотреть континуальное представление
ConnectionIO как альтернативу ConnectionRIO, что-то типа:type DB[F[_]] = [x] =>> ConnectionIO ~> F => F[x]
Этого оказалось достаточно, чтобы залифтить не только ConnectionIO, но и любой произвольный эффект F без промежуточных конверсий и явного извлечения контекста. Более того, такая форма позволяет почти бесплатно выводить большинство инстансов для DB через инстансы для F. Можно выкинуть огромную простыню дериваций лифтингов, которые я когда-то написал для ConnectionRIO.Больше всего я опасался невозможности выразить транзактор с сохранением API и поддержкой стримов. Но к счастью,
doobie.Transactor задизайнен достаточно хорошо, чтобы это стало осуществимым — интерпретация и транзакционная стратегия легко отделимы друг от друга. Всё получилось даже на CE2. Жаль, я не додумался до континуального представления раньше. Спасибо дедушке Йонеде за нашу прекрасную молодость.
Отправил сегодня PR в
tofu. Там есть небольшой пример использования (вместо доки), он почти не изменился — наоборот, кое в чём стал даже проще. В целом этот подход я воспринимаю как гораздо более правильный и чистый. Надеюсь, в последний момент не вскроется что-нибудь критичное. Что касается LogHandler — работа с ним стала чуть проще, за счет инстанса UnliftIO[DB], но когда мигрируем tofu на CE3 — окончательно с ним разберусь.Кстати, по миграции на CE3 стало заметно, как Rob Norris забросил развитие
doobie и посвятил все силы skunk'у. Последний я пока не распробовал — смущает собственная имплементация Postgres-протокола. Впрочем, слышал от уважаемых юзеров хорошие отзывы. Надо будет затащить куда-нибудь и сравнить их эргономику. Как минимум, трейсинг в skunk уже вшит, это обнадеживает.Хех, покопался в истории чата @tofu_ru — оказывается, мы ещё 2 апреля обсуждали с @odomontois возможное решение через континуэйшены. Но тогда мне этот вариант энкодинга показался сложным в имплементации всей нужной обвязки, и я его сам отбросил, так толком и не попробовав.
Хорошо, что я об этом успешно забыл и не побоялся вчера запилить всё с нуля.
Хорошо, что я об этом успешно забыл и не побоялся вчера запилить всё с нуля.
Вот и закончилась полугодовая эпопея миграции одной библиотеки на Cats Effect 3, а вдогонку — и на Scala 3. Вчера отправил в Sonatype больше 50 артефактов (160, если считать кросс-билды) и ещё несколько образов на Dockerhub. Спешу поделиться ощущениями, пока они ещё свежи.
Миграция Typelevel экосистемы на CE3 до сих пор буксует. Многие базовые библиотеки типичного CRUD-стэка либо только-только разродились стабильными релизами (http4s), либо находятся на этапе RC и даже майлстоунов (doobie, tapir), либо вообще ещё не имеют полной поддержки (tofu).
Просто напомню, что с момента стабильного релиза CE3 прошло больше 4 месяцев, а RC были доступны задолго до этого.
Я наблюдал за миграцией многих библиотек, и основные проблемы, которые я заметил, были связаны с использованием плохих абстракций (привет ConcurrentEffect) и смешным игнорированиемтотала Reader-паттерна. Для многих стало сюрпризом, что теперь нельзя взять и запустить
Очень жаль, что оригинальный (откровенно плохой) дизайн первых Cats Effect сыграл злую шутку с tagless final и сделал для его похорон гораздо больше, чем ZIO. Остается лишь процитировать классика — он оказался чертовски прав. Ещё печальнее, что даже после трёхлетней работы над новым дизайном там остались изъяны, которые теперь уже никогда не будут устранены в угоду обратной совместимости. Неприятно было наблюдать в dev-каналах гиттера и дискорда Typelevel, как неплохие идеи зарубили аргументами о том, что юзеры болезненно воспринимают любые изменения, требующие что-то переписать. К слову, сами авторы надеются, что CE4 никогда не появится (т.е. всё отлито в граните и кардинально уже не изменится). С таким подходом поезд ZIO будет уже не догнать.
Тяжелее всего миграция далась библиотекам, которые имеют кучу интеграций и обязаны их поддерживать на своей стороне. Например, в trace4cats количество блокеров, не включая транзитивных, в начале пути превышало десяток. Просто дождаться от каждого из них хотя бы майлстоуна заняло 3 месяца. Ещё столько же времени ушло на их стабилизацию. Это стало основной мотивацией для распиливания монорепы аж на 20 репозиториев, чтобы иметь возможность релизить их независимо.
Другой пример — tapir. Они всё ещё сидят в монорепе и несколько месяцев не могли сделать первый M-релиз из-за одного единственного блокера — catbird. Им раньше занимался Трэвис, но потом он самоустранился и отдал миграцию в руки Typelevel-сообщества. Бесхозный статус сильно замедлил переход на CE3 и сделал эту библиотеку блокером для tapir'а и (транзитивно) для нас.
Но, несмотря на все сложности, я очень доволен результатом в trace4cats: отрефакторили многие вещи, исправили кучу багов, а фиксы бэкпортировали в CE2-совместимую версию, воплотили новые идеи, реализовать которые под СЕ2 было бы достаточно сложно, а в СЕ3 оказалось возможно и очень интересно.
В общем, теперь всё готово, можно брать и тащить в прод!
Миграция Typelevel экосистемы на CE3 до сих пор буксует. Многие базовые библиотеки типичного CRUD-стэка либо только-только разродились стабильными релизами (http4s), либо находятся на этапе RC и даже майлстоунов (doobie, tapir), либо вообще ещё не имеют полной поддержки (tofu).
Просто напомню, что с момента стабильного релиза CE3 прошло больше 4 месяцев, а RC были доступны задолго до этого.
Я наблюдал за миграцией многих библиотек, и основные проблемы, которые я заметил, были связаны с использованием плохих абстракций (привет ConcurrentEffect) и смешным игнорированием
F[A] просто так, без какого-то контекста и рантайма. А когда это всё же удаётся, не всегда получается сделать это правильно (конструктор Dispatcher'а создает его как ресурс, теряющий локальные модификации контекста и отменяющий все запущенные файберы по выходу из use-скоупа). С другой стороны, в официальном около-CE-стэке никогда и не было всех необходимых тайпклассов для полноценной работы с контекстными эффектами.Очень жаль, что оригинальный (откровенно плохой) дизайн первых Cats Effect сыграл злую шутку с tagless final и сделал для его похорон гораздо больше, чем ZIO. Остается лишь процитировать классика — он оказался чертовски прав. Ещё печальнее, что даже после трёхлетней работы над новым дизайном там остались изъяны, которые теперь уже никогда не будут устранены в угоду обратной совместимости. Неприятно было наблюдать в dev-каналах гиттера и дискорда Typelevel, как неплохие идеи зарубили аргументами о том, что юзеры болезненно воспринимают любые изменения, требующие что-то переписать. К слову, сами авторы надеются, что CE4 никогда не появится (т.е. всё отлито в граните и кардинально уже не изменится). С таким подходом поезд ZIO будет уже не догнать.
Тяжелее всего миграция далась библиотекам, которые имеют кучу интеграций и обязаны их поддерживать на своей стороне. Например, в trace4cats количество блокеров, не включая транзитивных, в начале пути превышало десяток. Просто дождаться от каждого из них хотя бы майлстоуна заняло 3 месяца. Ещё столько же времени ушло на их стабилизацию. Это стало основной мотивацией для распиливания монорепы аж на 20 репозиториев, чтобы иметь возможность релизить их независимо.
Другой пример — tapir. Они всё ещё сидят в монорепе и несколько месяцев не могли сделать первый M-релиз из-за одного единственного блокера — catbird. Им раньше занимался Трэвис, но потом он самоустранился и отдал миграцию в руки Typelevel-сообщества. Бесхозный статус сильно замедлил переход на CE3 и сделал эту библиотеку блокером для tapir'а и (транзитивно) для нас.
Но, несмотря на все сложности, я очень доволен результатом в trace4cats: отрефакторили многие вещи, исправили кучу багов, а фиксы бэкпортировали в CE2-совместимую версию, воплотили новые идеи, реализовать которые под СЕ2 было бы достаточно сложно, а в СЕ3 оказалось возможно и очень интересно.
В общем, теперь всё готово, можно брать и тащить в прод!
👍1
Да, в заключение добавлю про Scala 3. Ишшу на добавление кросс-билда trace4cats я завёл ещё в январе — на месяц раньше, чем для миграции на CE3, но всерьёз этим мы начали заниматься совсем недавно. И здесь впечатления гораздо приятнее.
Надо отдать должное команде Dotty за титанический труд, благодаря которому весь старый код практически без изменений скомпилировался под Scala 3. Да, пришлось выкинуть bm4 и переписать kind-projector'ные полимфорные лямбды, но эти мелочи к самому языку не относятся и скорее являются проблемами только при кросс-билде, а не полноценном переходе. Было ещё несколько совсем неожиданных ошибок с выводом некоторых имплиситов поверх ZIO, но это просто баги компилятора, которые обязательно починят позже (надо не забыть зарепортить их).
А вот портирование макросов — это настоящая беда. Благо, своих у нас нет, зато используются сторонние дериваторы на magnolia и кастомных макросах. Пришлось (как минимум — на какое-то время) заменить их на вручную написанные инстансы.
Но и из этого удалось вынести кое-какую пользу. Например, при тестировании оказалось, что автовыводимые avro-кодеки в vulcan (внезапно) генерируют схемы с разным порядком сумм на Scala 2 (в оригинальной имплементации на magnolia) и на Scala 3 (пока что только в POC-имплементации на shapeless, т.к. magnolia — RIP). Зато вывести их руками для весьма развесистой модели заняло полчаса, и есть уверенность, что схема будет стабильной.
Были и пара случаев, когда я обнаруживал проблемы в версии какой-нибудь либы под Scala 3, бежал делать PR, но пока его принимали, мержили и релизили, я перегорал от ожидания и просто выпиливал эту зависимость, заменив чем-то попроще. На самом деле очень рад, что из-за косяка в кросс-билде scalapb мы вообще избавились от protobuf в одном модуле и избежали использования scalapb-json4s (от кодеков которого остались ужасные впечатления с какими-то жуткими java-флэшбэками).
Буквально за пару недель удалось всё сделать и даже не задерживать давно запланированный релиз trace4cats под CE3. Мне Scala 3 тащить в прод пока негде, но один из помощников в миграции обещал внедрить новую версию у себя at
Надо отдать должное команде Dotty за титанический труд, благодаря которому весь старый код практически без изменений скомпилировался под Scala 3. Да, пришлось выкинуть bm4 и переписать kind-projector'ные полимфорные лямбды, но эти мелочи к самому языку не относятся и скорее являются проблемами только при кросс-билде, а не полноценном переходе. Было ещё несколько совсем неожиданных ошибок с выводом некоторых имплиситов поверх ZIO, но это просто баги компилятора, которые обязательно починят позже (надо не забыть зарепортить их).
А вот портирование макросов — это настоящая беда. Благо, своих у нас нет, зато используются сторонние дериваторы на magnolia и кастомных макросах. Пришлось (как минимум — на какое-то время) заменить их на вручную написанные инстансы.
Но и из этого удалось вынести кое-какую пользу. Например, при тестировании оказалось, что автовыводимые avro-кодеки в vulcan (внезапно) генерируют схемы с разным порядком сумм на Scala 2 (в оригинальной имплементации на magnolia) и на Scala 3 (пока что только в POC-имплементации на shapeless, т.к. magnolia — RIP). Зато вывести их руками для весьма развесистой модели заняло полчаса, и есть уверенность, что схема будет стабильной.
Были и пара случаев, когда я обнаруживал проблемы в версии какой-нибудь либы под Scala 3, бежал делать PR, но пока его принимали, мержили и релизили, я перегорал от ожидания и просто выпиливал эту зависимость, заменив чем-то попроще. На самом деле очень рад, что из-за косяка в кросс-билде scalapb мы вообще избавились от protobuf в одном модуле и избежали использования scalapb-json4s (от кодеков которого остались ужасные впечатления с какими-то жуткими java-флэшбэками).
Буквально за пару недель удалось всё сделать и даже не задерживать давно запланированный релиз trace4cats под CE3. Мне Scala 3 тащить в прод пока негде, но один из помощников в миграции обещал внедрить новую версию у себя at
$WORK, где они уже всё остальное перевели (какие же они молодцы).Scala Steward — одна из моих самых любимых утилит в Scala-экосистеме и лучший помощник любому проекту в борьбе с техдолгом в части обновления зависимостей. Он умеет не просто бампить версии плагинов и либ, но и прогонять sbt-таски после этого: переформатировать проект после обновления
Но одной фичи там очень долго и очень сильно не хватало — поддержки проектов с несколькими активными бранчами. В
И наконец эту фичу добавили — теперь Стюарду можно указать, в какой бранч репозитория следует присылать PR'ы. С этого дня жизнь мэйнтейнеров станет немного проще.
P.S. Напомню, что Стюард прикручивается и к приватным репозиториям, и не только на github. Так что если вы в своих проектах до сих пор бампите версии руками раз в 3 месяца (а потом тратите ещё день, чтобы проект скомпилировался после массового апгрейда стэка), бегом настраивать Стюарда!
scalafmt, прогнать scalafix'ы и даже перегенерировать yaml'ы в .github/workflows — этим он выгодно отличается от аналогов типа Renovate именно для Scala-проектов.Но одной фичи там очень долго и очень сильно не хватало — поддержки проектов с несколькими активными бранчами. В
http4s их, например, аж четыре. Какое-то время назад и мне пришлось столкнуться с необходимостью поддержки двух активных версий библиотеки после сплита Typelevel-экосистемы на CE2/CE3.И наконец эту фичу добавили — теперь Стюарду можно указать, в какой бранч репозитория следует присылать PR'ы. С этого дня жизнь мэйнтейнеров станет немного проще.
P.S. Напомню, что Стюард прикручивается и к приватным репозиториям, и не только на github. Так что если вы в своих проектах до сих пор бампите версии руками раз в 3 месяца (а потом тратите ещё день, чтобы проект скомпилировался после массового апгрейда стэка), бегом настраивать Стюарда!
В Scala 2 никогда не было обратной совместимости между мажорным версиями, а до 2.10 — и между минорными. Напомню, что применяется схема версионирования epoch.major.minor, как и у Java.
Многих эта несовместимость отталкивала от использования Scala в долгосрочных проектах. Неумение или нежелание работать с техдолгом могли всего за несколько лет привести к полному протуханию кодовой базы и невозможности обновить стэк. Я видел энтерпрайз, навечно застрявший на 2.11 без какой-либо надежды на миграцию хотя бы на 2.13, не говоря уже про 3.0.
Повышение версии компилятора и стандартной библиотеки в крупных проектах порой требовало титанических усилий. Например, команда Apache Spark — в своё время одного из главных бустеров популярности Scala — потратила на миграцию на 2.13 больше двух лет и завершила её на днях с выходом версии 3.2.
С самого начала становления экосистемы Scala перед авторами библиотек возник вопрос — как версионировать свои артефакты, чтобы обеспечить их корректное использование на разных версиях компилятора, не превратив управление зависимостями в ад. Появилась концепция cross-building'а — сборки и публикации Maven-артефактов одной версии с разными суффиксами в имени, обозначающими бинарную совместимость с конкретной версией Scala, например
Команда разработки Scala 3 совершила маленькую революцию — ввела схему версионирования major.minor.patch и гарантировала обратную совместимость для всех будущих версий 3.x.y. Суффикс в именах maven-артефактов сократился до
Праздник длился всего несколько часов, пока Seth Tisue не запостил трэд, в котором разъяснил все последствия этих изменений. Scala 3 гарантирует обратную совместимость, но не гарантирует, а точнее, вовсе не поддерживает forward-совместимость, во всяком случае пока. Попытка использовать артефакт, полученный от старшей минорной версии компилятора, приводит к ошибкам в compile-time на младшей. Пока что нет и поддержки в sbt, чтобы обнаруживать это во время разрешения зависимостей. А это значит, что библиотекам не стоит спешить с переходом на Scala 3.1, если они не хотят оставлять своих пользователей, по каким-то причинам застрявшим на предыдущей версии компилятора, без дальнейшей поддержки. Ведь теперь нельзя скроссбилдить и опубликовать артефакт под разные версии Scala 3.x, как это было раньше.
На самом деле эта проблема уже много лет существует в Java-экосистеме, где авторам библиотек приходится сидеть в лучшем случае на jdk8. Она же всегда существовала, но оставалась малозаметной и в Scala.js. Но мэйнстримовое Scala-сообщество оказалось к ней не готово. Кто-то вообще её не увидел, кто-то начал сглаживать углы, кто-то — откатывать влитые PR'ы, кто-то — откровенно саботировать любые попытки договориться об общих правилах.
В данный момент дискуссия по существу переехала на Github в комментарии к одному из PR'ов. Наиболее адекватно выглядят аргументы Ross Baker — основного мэйнтейнера http4s, выпустившего за последние месяцы ряд CVE-патчей сразу к четырем активным веткам этого проекта. Он описал разные подходы к решению проблемы, из которых мне ближе всего последний — билдить проекты на разных субверсиях Scala 3, но публиковать пока только под 3.0.2.
P.S. Пока я писал этот пост, вышел официальный анонс релиза Scala 3.1, в котором рекомендуют тот самый Scenario C, предложенный Россом, и обещают сделать всё возможное для добавления forward-compatibility в Scala 3.2.0. Вот это поворот! Надеюсь, у команды компилятора всё получится.
Многих эта несовместимость отталкивала от использования Scala в долгосрочных проектах. Неумение или нежелание работать с техдолгом могли всего за несколько лет привести к полному протуханию кодовой базы и невозможности обновить стэк. Я видел энтерпрайз, навечно застрявший на 2.11 без какой-либо надежды на миграцию хотя бы на 2.13, не говоря уже про 3.0.
Повышение версии компилятора и стандартной библиотеки в крупных проектах порой требовало титанических усилий. Например, команда Apache Spark — в своё время одного из главных бустеров популярности Scala — потратила на миграцию на 2.13 больше двух лет и завершила её на днях с выходом версии 3.2.
С самого начала становления экосистемы Scala перед авторами библиотек возник вопрос — как версионировать свои артефакты, чтобы обеспечить их корректное использование на разных версиях компилятора, не превратив управление зависимостями в ад. Появилась концепция cross-building'а — сборки и публикации Maven-артефактов одной версии с разными суффиксами в имени, обозначающими бинарную совместимость с конкретной версией Scala, например
org.typelevel:cats-core_2.13:1.0.0. При всех минусах отсутствия обратной совместимости кроссбилдинг был приемлемым решением, а благодаря поддержке в sbt — и весьма удобным.Команда разработки Scala 3 совершила маленькую революцию — ввела схему версионирования major.minor.patch и гарантировала обратную совместимость для всех будущих версий 3.x.y. Суффикс в именах maven-артефактов сократился до
_3. Старожилы Scala-сообщества сначала не верили в избавление от необходимости кроссбилдить библиотеки под разные субверсии Scala 3, а потом не могли сдержать радости. Ликование усилилось с выходом Scala 3.1, когда каждый мэйнтейнер смог лично убедиться, что библиотеки, опубликованные для 3.0, успешно работают с новым компилятором.Праздник длился всего несколько часов, пока Seth Tisue не запостил трэд, в котором разъяснил все последствия этих изменений. Scala 3 гарантирует обратную совместимость, но не гарантирует, а точнее, вовсе не поддерживает forward-совместимость, во всяком случае пока. Попытка использовать артефакт, полученный от старшей минорной версии компилятора, приводит к ошибкам в compile-time на младшей. Пока что нет и поддержки в sbt, чтобы обнаруживать это во время разрешения зависимостей. А это значит, что библиотекам не стоит спешить с переходом на Scala 3.1, если они не хотят оставлять своих пользователей, по каким-то причинам застрявшим на предыдущей версии компилятора, без дальнейшей поддержки. Ведь теперь нельзя скроссбилдить и опубликовать артефакт под разные версии Scala 3.x, как это было раньше.
На самом деле эта проблема уже много лет существует в Java-экосистеме, где авторам библиотек приходится сидеть в лучшем случае на jdk8. Она же всегда существовала, но оставалась малозаметной и в Scala.js. Но мэйнстримовое Scala-сообщество оказалось к ней не готово. Кто-то вообще её не увидел, кто-то начал сглаживать углы, кто-то — откатывать влитые PR'ы, кто-то — откровенно саботировать любые попытки договориться об общих правилах.
В данный момент дискуссия по существу переехала на Github в комментарии к одному из PR'ов. Наиболее адекватно выглядят аргументы Ross Baker — основного мэйнтейнера http4s, выпустившего за последние месяцы ряд CVE-патчей сразу к четырем активным веткам этого проекта. Он описал разные подходы к решению проблемы, из которых мне ближе всего последний — билдить проекты на разных субверсиях Scala 3, но публиковать пока только под 3.0.2.
P.S. Пока я писал этот пост, вышел официальный анонс релиза Scala 3.1, в котором рекомендуют тот самый Scenario C, предложенный Россом, и обещают сделать всё возможное для добавления forward-compatibility в Scala 3.2.0. Вот это поворот! Надеюсь, у команды компилятора всё получится.
👍2
Big Flatmappa
В Scala 2 никогда не было обратной совместимости между мажорным версиями, а до 2.10 — и между минорными. Напомню, что применяется схема версионирования epoch.major.minor, как и у Java. Многих эта несовместимость отталкивала от использования Scala в долгосрочных…
А вот и план по введению forward compatibility. Флаг
--scala-target появится уже в 3.1.1!Scala Contributors
Improving Scala 3 forward compatibility
Scala 3 has very good backward compatibility guarantees between the minor versions. On the other hand, after the recent release of Scala 3.1, we can see that the libraries should be really cautious with updating the compiler version, as it is forcing the…