Написал небольшую библиотеку с интеграцией ZIO и MUnit
https://github.com/poslegm/munit-zio
Мне нравится MUnit за его простоту, минималистичность и наглядные сообщения об ошибках. Чего нельзя сказать о «нативном» для ZIO zio-test: это очень сложный фреймворк с кучей ассершенов, незадокументированных методов провайдинга зависимостей и аспектов.
На мой взгляд тесты должны быть простыми, поэтому и захотелось тестировать свои монадки на MUnit.
А ещё вся документация по munit-zio умещается в README, а полной документации по zio-test в принципе не существует.
https://github.com/poslegm/munit-zio
Мне нравится MUnit за его простоту, минималистичность и наглядные сообщения об ошибках. Чего нельзя сказать о «нативном» для ZIO zio-test: это очень сложный фреймворк с кучей ассершенов, незадокументированных методов провайдинга зависимостей и аспектов.
На мой взгляд тесты должны быть простыми, поэтому и захотелось тестировать свои монадки на MUnit.
А ещё вся документация по munit-zio умещается в README, а полной документации по zio-test в принципе не существует.
scalameta.org
MUnit · Scala testing library with actionable errors and extensible APIs
Forwarded from Big Flatmappa
В экосистеме 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 сделал за несколько минут.
Big Flatmappa
В экосистеме Cats Effect есть утилита Hotswap, предназначенная для управления жизненным циклом заменяемых "на горячую" ресурсов через общий ресурсный скоуп. К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от…
👆Новый канал про монадки, спешите подписаться!
Ковырялся на работе с тапиром, не понравилось. tapir — это популярный в Scala-мире DSL для описания HTTP эндпоинтов. DSL интерпретируется в “настоящие” эндпоинты на одном из четырёх доступных веб-серверов и автоматически генерирует спецификацию в OpenAPI. Собственно, ради автогенерации сваггера мы и затащили его в один REST-сервис. Руками OpenAPI описывать всё-таки нудно и ненадёжно, а gRPC не подходил по требованиям к сервису.
Основную боль причинила аутентификация: она дырявая и неудобная. Тапир сначала парсит метод и путь запроса, а только потом проверяет наличие Auth-заголовка. Значит злоумышленник может пореверс-инжинирить контракт, получая осмысленные 400 и 404 вместо 401. Если написать код аутентификации так, как предлагает документация, сервер ещё и тело запроса парсит 🤦♂️. Уязвимость с телом я обошёл костылём, а вот чтобы заткнуть дыру с парсингом пути, пришлось кинуть PR (надеюсь, примут). Правда код из документации всё равно останется дырявым by design, извольте писать костыли.
Следующая боль — маппинг ошибок. Тапир заставляет маппить эксепшены в HTTP-коды один к одному. При этом компиляторных гарантий полноты маппинга нет… Естественно, всё многообразие ошибок бизнес-логики 1-к-1 на HTTP-коды не замаппишь, поэтому приходится делать промежуточную ADT с сетевыми ошибками, а уже её маппить в коды.
Но и такой маппинг нельзя красиво встроить в код эндпоинтов, потому что в тапире нет абстракции для middleware! Поэтому всякие маппинги ошибок, логирование и тд приходится вкорячивать либо непосредственно в веб-сервер (если до него дойдёт всё, что хочется залогировать), либо костылить всратыми адхоками.
Спасибо, Господи, за gRPC и GraphQL.
Основную боль причинила аутентификация: она дырявая и неудобная. Тапир сначала парсит метод и путь запроса, а только потом проверяет наличие Auth-заголовка. Значит злоумышленник может пореверс-инжинирить контракт, получая осмысленные 400 и 404 вместо 401. Если написать код аутентификации так, как предлагает документация, сервер ещё и тело запроса парсит 🤦♂️. Уязвимость с телом я обошёл костылём, а вот чтобы заткнуть дыру с парсингом пути, пришлось кинуть PR (надеюсь, примут). Правда код из документации всё равно останется дырявым by design, извольте писать костыли.
Следующая боль — маппинг ошибок. Тапир заставляет маппить эксепшены в HTTP-коды один к одному. При этом компиляторных гарантий полноты маппинга нет… Естественно, всё многообразие ошибок бизнес-логики 1-к-1 на HTTP-коды не замаппишь, поэтому приходится делать промежуточную ADT с сетевыми ошибками, а уже её маппить в коды.
Но и такой маппинг нельзя красиво встроить в код эндпоинтов, потому что в тапире нет абстракции для middleware! Поэтому всякие маппинги ошибок, логирование и тд приходится вкорячивать либо непосредственно в веб-сервер (если до него дойдёт всё, что хочется залогировать), либо костылить всратыми адхоками.
Спасибо, Господи, за gRPC и GraphQL.
Задача (де)сериализации структур в json решается тремя способами:
1. Проанализировать поля в рантайме с помощью рефлексии;
2. "Заранее" статически проанализировать структуры и нагенерировать для них кодеки;
3. Переложить все поля класса в json и обратно руками :)
В Scala по первому пути идёт, например, старая библиотека json4s. Но рантайм-рефлексия работает в несколько раз медленнее заранее сгенерированного кода, а сериализация частенько становится узким местом высоконагруженных приложений. Поэтому сообщество сместилось в сторону второго подхода, который реализует библиотека circe.
Паттерн у генерации всяческих сериализаторов на Scala примерно одинаковый: объявляется тайпкласс с дефолтными инстансами для примитивов (String, Int, Boolean, etc...) и пользовательскими инстансами для своих типов. Для кейс-классов макросом (или встроенной деривацией в Scala 3) выводятся составные инстансы, в которых поля структуры сопоставляются полю в json и конвертируются кодеком для типа поля.
Результирующий код в рантайме ведёт себя так же быстро как перекладывание руками, но при этом кодек не нужно писать и поддерживать самостоятельно.
А вспомнил я об этом, потому что наткнулся на библиотеку ffjson, которая решает те же проблемы в голанге. Стандартный пакет использует рантайм-рефлексию, поэтому ffjson его обгоняет. Только вместо макросов там кодеки просто кодогенерируются перед сборкой проекта. Языки разные, а проблемы у всех одинаковые :)
1. Проанализировать поля в рантайме с помощью рефлексии;
2. "Заранее" статически проанализировать структуры и нагенерировать для них кодеки;
3. Переложить все поля класса в json и обратно руками :)
В Scala по первому пути идёт, например, старая библиотека json4s. Но рантайм-рефлексия работает в несколько раз медленнее заранее сгенерированного кода, а сериализация частенько становится узким местом высоконагруженных приложений. Поэтому сообщество сместилось в сторону второго подхода, который реализует библиотека circe.
Паттерн у генерации всяческих сериализаторов на Scala примерно одинаковый: объявляется тайпкласс с дефолтными инстансами для примитивов (String, Int, Boolean, etc...) и пользовательскими инстансами для своих типов. Для кейс-классов макросом (или встроенной деривацией в Scala 3) выводятся составные инстансы, в которых поля структуры сопоставляются полю в json и конвертируются кодеком для типа поля.
trait Encoder[T] {
def encode(obj: T): Json
}
implicit val stringEncoder: Encoder[String] =
(str: String) => Json.fromString(str)
@JsonCodec case class Sample(stringValue: String)Результирующий код в рантайме ведёт себя так же быстро как перекладывание руками, но при этом кодек не нужно писать и поддерживать самостоятельно.
А вспомнил я об этом, потому что наткнулся на библиотеку ffjson, которая решает те же проблемы в голанге. Стандартный пакет использует рантайм-рефлексию, поэтому ffjson его обгоняет. Только вместо макросов там кодеки просто кодогенерируются перед сборкой проекта. Языки разные, а проблемы у всех одинаковые :)
Поигрался с match types в Scala 3. Сильно хотел использовать их для определения зависимости возвращаемого типа функции от типа аргумента, но не прокатило. Не понравилось, что матчинг при использовании типа происходит в рантайме, поэтому можно словить исключение
Полный пример в Scastie. Для переменной
Второе следствие матчинга в рантайме — потеря ленивости аргументов:
Полный пример в Scastie.
Пока не понимаю, является ли матчинг в рантайме следствием фундаментальных ограничений языка. Но вообще хотелось бы иметь возможность без костылей сделать зависимую типизацию для своей функции, просто разматчив тип аргумента.
Появление summonFrom и transparent inline позволяет накостылить нечто подобное чуть менее вербозно, чем в Scala 2, но всё равно не интуитивно.
Пример на Scala 2: https://github.com/zio/zio/issues/5241
Он же на Scala 3: https://scastie.scala-lang.org/EwfBu7rcTwi6UQ11VYMwEQ
Несмотря на то, что эта штука работает, выглядит она не как нативная языковая
конструкция, а как насилие над компилятором.
MatchError:type Invert[T] = T match
case String => Int
case Int => String
def invert[T](t: T): Invert[T] =
t match
case s: String => s.length
case i: Int => i.toString
val test = invert(false) // упадёт в рантайме
Полный пример в Scastie. Для переменной
test компилятор выводит тип Invert[Boolean], но не проверяет, есть ли такая ветка в определении типа. В итоге код компилируется, но падает при запуске.Второе следствие матчинга в рантайме — потеря ленивости аргументов:
type Eval[T] = T match
case Any => "do nothing"
def eval[T](t: => T): Eval[T] =
t match
case _: Any => "do nothing"
val a = eval {
println("oooops")
42
}
Полный пример в Scastie.
Пока не понимаю, является ли матчинг в рантайме следствием фундаментальных ограничений языка. Но вообще хотелось бы иметь возможность без костылей сделать зависимую типизацию для своей функции, просто разматчив тип аргумента.
Появление summonFrom и transparent inline позволяет накостылить нечто подобное чуть менее вербозно, чем в Scala 2, но всё равно не интуитивно.
Пример на Scala 2: https://github.com/zio/zio/issues/5241
Он же на Scala 3: https://scastie.scala-lang.org/EwfBu7rcTwi6UQ11VYMwEQ
Несмотря на то, что эта штука работает, выглядит она не как нативная языковая
конструкция, а как насилие над компилятором.
Lil Functor
Поигрался с match types в Scala 3. Сильно хотел использовать их для определения зависимости возвращаемого типа функции от типа аргумента, но не прокатило. Не понравилось, что матчинг при использовании типа происходит в рантайме, поэтому можно словить исключение…
Добрый человек в комментах подсказал, что первая проблема решается флагом
-Ycheck-all-patmat. Очень странно, что он не включен по дефолту.Напоминаю, что Яндекс.Вертикали нанимают скалистов!
Мы делаем три площадки объявлений: auto.ru, Я.Недвижимость и Я.Объявления. Подробный рассказ о RSU, техническом стеке и стульях Herman Miller можно прочитать в вакансии: https://telegra.ph/Vakansiya-behkend-razrabotchika-na-Scala-03-04
Со своей колокольни могу добавить, что вам особенно нужно в Вертикали, если:
– вы планируете переход из разработки в управление. В этом случае у вас будет синергия с постоянной потребностью лидов разработки в Вертикалях;
– вы вэлью-челик с интересом к продукту и сильной технической базой. Вам будет комфортно и не скучно работать в продуктовых командах;
– вы хотите перейти из джавы в скалу. У нас есть опыт адаптации джавистов, а размер компании обеспечит плавный переход;
– вы работаете в стартапе и хотите попробовать бигтех на вкус. Вертикали достаточно небольшой юнит по меркам Яндекса, поэтому шока от перехода в корпорацию почти не будет. А вот все плюшки — будут.
Резюме и вопросы присылайте в лс -> @poslegm
И самое главное: нельзя. игнорировать. тотал.
Мы делаем три площадки объявлений: auto.ru, Я.Недвижимость и Я.Объявления. Подробный рассказ о RSU, техническом стеке и стульях Herman Miller можно прочитать в вакансии: https://telegra.ph/Vakansiya-behkend-razrabotchika-na-Scala-03-04
Со своей колокольни могу добавить, что вам особенно нужно в Вертикали, если:
– вы планируете переход из разработки в управление. В этом случае у вас будет синергия с постоянной потребностью лидов разработки в Вертикалях;
– вы вэлью-челик с интересом к продукту и сильной технической базой. Вам будет комфортно и не скучно работать в продуктовых командах;
– вы хотите перейти из джавы в скалу. У нас есть опыт адаптации джавистов, а размер компании обеспечит плавный переход;
– вы работаете в стартапе и хотите попробовать бигтех на вкус. Вертикали достаточно небольшой юнит по меркам Яндекса, поэтому шока от перехода в корпорацию почти не будет. А вот все плюшки — будут.
Резюме и вопросы присылайте в лс -> @poslegm
И самое главное: нельзя. игнорировать. тотал.
Нашёл библиотечку OCDQuery с генерацией запросов на doobie по пользовательским моделям данных. Сама библиотека выглядит заброшенной, а вот в документации лежит сокровище — понятное описание проблематики и реализации паттерна Higher Kinded Data для моделей сущностей из БД.
https://scalalandio.github.io/ocdquery/#Initialidea
Проблема: часть колонок заполняются БД по заданным правилам (автоинкремент id, например). Соответственно, они присутствуют в модели, которая из базы читается, но бесполезны в той, которая в базу пишется. Ещё при накатывании миграций нужны не значения полей, а названия соответствующих колонок.
Можно решать это отдельными моделями на каждый вариант использования, можно накостылить
Описываем кейс-класс, поля которого лежат в контейнерах, тип которых задаётся при создании. Под каждый способ использования модели задаётся комбинация контейнеров.
Количество тайп-параметров и их комбинаций можно делать любое в зависимости от сценариев использования данных. Главное не получить комбинаторный взрыв :)
https://scalalandio.github.io/ocdquery/#Initialidea
Проблема: часть колонок заполняются БД по заданным правилам (автоинкремент id, например). Соответственно, они присутствуют в модели, которая из базы читается, но бесполезны в той, которая в базу пишется. Ещё при накатывании миграций нужны не значения полей, а названия соответствующих колонок.
Можно решать это отдельными моделями на каждый вариант использования, можно накостылить
null/Option на все колонки. А можно применить HKD.Описываем кейс-класс, поля которого лежат в контейнерах, тип которых задаётся при создании. Под каждый способ использования модели задаётся комбинация контейнеров.
case class ColumnName(name: String)
type Id[A] = A // когда поле обязательно присутствует
type UnitF[A] = Unit // когда поле будет создано базой
type ColumnNameF[A] = ColumnName // для миграций
// F для пользовательских полей
// С для полей, управляемых базой
case class User[F[_], C[_]](id: C[String], name: F[String])
type UserSelect = User[Id, Id] // все поля будут присутствовать в модели
type UserInsert = User[Id, UnitF] // поля, управляемые базой, будут заполнены Unit
type UserColumns = User[ColumnNameF, ColumnNameF] // вместо всех полей будут экземпляры ColumnNames
Количество тайп-параметров и их комбинаций можно делать любое в зависимости от сценариев использования данных. Главное не получить комбинаторный взрыв :)
Не сразу заметно, но в документации третьей скалы работает аналог Hoogle. Hoogle — это поисковый движок по библиотекам на Haskell, умеющий искать по сигнатуре функции. В документации Scala 3 тоже можно нажать на лупу и ввести, например,
Поиск сделан на библиотеке Inquire. Как подключить её в свой скаладок, не понятно. Ещё в Hoogle проиндексированы все хаскельные библиотеки, а Inquire умеет искать только по текущей.
Пока выглядит скорее как игрушка, но вот если бы в IDEA или Metals появилось аналогичное решение с поиском по сигнатурам в проекте и его зависимостях, было бы прямо круто.
Option[A] => A => Boolean: поиск найдёт функцию contains. Правда, далеко не на первом месте.Поиск сделан на библиотеке Inquire. Как подключить её в свой скаладок, не понятно. Ещё в Hoogle проиндексированы все хаскельные библиотеки, а Inquire умеет искать только по текущей.
Пока выглядит скорее как игрушка, но вот если бы в IDEA или Metals появилось аналогичное решение с поиском по сигнатурам в проекте и его зависимостях, было бы прямо круто.
От чтения тайпскриптовых ФП-библиотек (раз, два) складывается впечатление, что они проходят тот же этап, что и скалисты несколько лет назад, когда тащили всё подряд из хаскеля. Только в тайпскрипте это выглядит как будто бы ещё хуже: один только заменитель do-нотации чего стоит. В исходниках ещё много вещей, которые интересны своей изобретательностью в обходе ограничений компилятора, но болезнены в использовании.
Похоже, что "переводы" хаскеля — это проклятие всех языков с развитой системой типов, а идиоматичный инструментарий для ФП появляется только спустя годы экспериментов.
Система типов в TS при этом передовая для промышленного языка: условные типы, типы-литералы даже с поддержкой шаблонов, flow typing, пересечения и объединения типов. Часть этих фич только появилась в Scala 3, а в тайпскрипте существует уже несколько лет. Возможно, тайплевельные истории там не летят из-за интеропа с JS, который гораздо более неприятный, чем интероп с джавой в Scala-мире.
Похоже, что "переводы" хаскеля — это проклятие всех языков с развитой системой типов, а идиоматичный инструментарий для ФП появляется только спустя годы экспериментов.
Система типов в TS при этом передовая для промышленного языка: условные типы, типы-литералы даже с поддержкой шаблонов, flow typing, пересечения и объединения типов. Часть этих фич только появилась в Scala 3, а в тайпскрипте существует уже несколько лет. Возможно, тайплевельные истории там не летят из-за интеропа с JS, который гораздо более неприятный, чем интероп с джавой в Scala-мире.
Есть один супер-интересный канал с разбором пейперов по распределённым системам, на котором несправедливо мало подписчиков.
Всем советую подписаться, там о больших и сложных вещах понятным языком рассказывают. Сегодня вот вышел пост о FoundationDB.
https://t.me/shark_in_it
Всем советую подписаться, там о больших и сложных вещах понятным языком рассказывают. Сегодня вот вышел пост о FoundationDB.
https://t.me/shark_in_it
Telegram
Акула (в) IT
FoundationDB: A Distributed Unbundled Transactional Key Value Store
#shark_whitepaper
Пока я пытаюсь осилить ARIES, немного ненапряжного и очень свежего case-study от июня 2021 года. Работа про распределенное KV хранилище FoundationDB. Всех авторов перечислять…
#shark_whitepaper
Пока я пытаюсь осилить ARIES, немного ненапряжного и очень свежего case-study от июня 2021 года. Работа про распределенное KV хранилище FoundationDB. Всех авторов перечислять…
Удобнейший плагин для релизов артефактов в Maven Central sbt-ci-release переехал в организацию sbt на гитхабе и похоже стал официально рекомендованным способом автоматизации релизов.
Это здорово, потому что релизить что-то в Maven Central — долгий и болезненный процесс, а с этим плагином всё как-то само происходит.
Это здорово, потому что релизить что-то в Maven Central — долгий и болезненный процесс, а с этим плагином всё как-то само происходит.
Канал перевалил за 1000 подписчиков, очень радостное для меня число! Пока вы не успели отписаться, сделаю стандартный телеграм-пост с каналами об ИТ, которые я читаю.
* @oleg_log, @oleg_fov — Олег пишет об индустрии. Удивляюсь его продуктивности и читаю, чтобы держать руку на пульсе;
* @bigflatmappa — канал контрибутора ФП-библиотек с историями о том, что он туда контрибутит. Стоит подписаться, чтобы проникнуться духом 10х программирования;
* @yourcybergrandpa — дед ворчит на облака;
* @architect_says — дед ворчит на Agile;
* @nikitonsky_pub — Никита Прокопов ворчит на всё вокруг;
* @nosingularity — о базах данных и инструментарии для них;
* @dereference_pointer_there — личный блог без чётко очерченной тематики (но частенько про Rust);
* @pmdaily — о продуктовой разработке и взаимоотношениях программиста с бизнесом;
* @scala_channel_ru — важные новости и анонсы из мира Scala;
* @daily_ponv — в основном ссылки на сложные пейперы;
* @shark_in_it — резюме пейперов о распределённых системах и базах данных;
* @scalabin — Антон давно ничего не писал, но если вдруг напишет — точно будет интересно;
* @consensus_io — о распределённых системах.
И конечно же чаты самого дружелюбного в мире сообщества, в котором высококвалифицированные специалисты помогают всем желающим стать 10x скалистом:
@scala_ru, @scala_learn, @scala_jobs, @ru_zio, @akka_ru
* @oleg_log, @oleg_fov — Олег пишет об индустрии. Удивляюсь его продуктивности и читаю, чтобы держать руку на пульсе;
* @bigflatmappa — канал контрибутора ФП-библиотек с историями о том, что он туда контрибутит. Стоит подписаться, чтобы проникнуться духом 10х программирования;
* @yourcybergrandpa — дед ворчит на облака;
* @architect_says — дед ворчит на Agile;
* @nikitonsky_pub — Никита Прокопов ворчит на всё вокруг;
* @nosingularity — о базах данных и инструментарии для них;
* @dereference_pointer_there — личный блог без чётко очерченной тематики (но частенько про Rust);
* @pmdaily — о продуктовой разработке и взаимоотношениях программиста с бизнесом;
* @scala_channel_ru — важные новости и анонсы из мира Scala;
* @daily_ponv — в основном ссылки на сложные пейперы;
* @shark_in_it — резюме пейперов о распределённых системах и базах данных;
* @scalabin — Антон давно ничего не писал, но если вдруг напишет — точно будет интересно;
* @consensus_io — о распределённых системах.
И конечно же чаты самого дружелюбного в мире сообщества, в котором высококвалифицированные специалисты помогают всем желающим стать 10x скалистом:
@scala_ru, @scala_learn, @scala_jobs, @ru_zio, @akka_ru
Посмотрел на Ruby Russia кейноут создателя языка. Там он всерёз говорит об оптимизации языка под микробенчмарки, потому что программисты слишком серьёзно к ним относятся, и выбирают языки, которые обгоняют Ruby. То есть буквально не из-за того, что ускорение рантайма принесёт какую-то ценность, а ради увеличения привлекательности языка. Дальше рассказывал про целый роадмап повышения перфоманса Ruby: многоуровневый JIT, гранты контрибуторам за микрооптимизации. И всё это под соусом того, что «медлительность» вредит маркетингу.
И вот не понимаю: программисты действительно столько внимания уделяют синтетическим бенчмаркам? Почему?
Конечно, есть критичные к перфомансу области разработки, но они уже заняты специфичными языками: Rust, C++. На Ruby пишутся в основном веб-бэкенды, а в них, чтобы упереться в производительность *языка*, надо особо постараться. Если в сервисе что-то тормозит, то почти всегда накосячил программист, а не рантайм. На своей практике припоминаю только два случая, когда тормоза на высоких нагрузках можно было с натяжкой списать на проблемы платформы. Оба связаны с (де-)сериализацией, что характерно :). Но и они решились не переписыванием со скалы на раст, а просто заменой библиотеки и небольшими оптимизациями кода.
А что до синтетических бенчмарков: в самом популярном сравнении HTTP серверов akka-http стабильно болтается около 190-го места. При этом в проде работает нормально и кушать не просит. Если что-то и тормозит, то в коде приложения, а не фреймворка. Современное состояние индустрии таково, что фреймворк со дна бенчмарков может держать внушительную нагрузку! И это не говоря о дешёвом железе.
Поэтому в моих глазах топ причин плохого перфоманса выглядит так:
1. баг в коде;
2. ошибка конфигурации;
3. ошибка дизайна системы;
4. ... программист обосрался где-то ещё ...
5. медленный рантайм языка.
Или в руби всё настолько плохо с производительностью, что даже скала по сравнению с ней летает? Или программистам комфортно списывать свои оплошности на «медленный язык»?
И вот не понимаю: программисты действительно столько внимания уделяют синтетическим бенчмаркам? Почему?
Конечно, есть критичные к перфомансу области разработки, но они уже заняты специфичными языками: Rust, C++. На Ruby пишутся в основном веб-бэкенды, а в них, чтобы упереться в производительность *языка*, надо особо постараться. Если в сервисе что-то тормозит, то почти всегда накосячил программист, а не рантайм. На своей практике припоминаю только два случая, когда тормоза на высоких нагрузках можно было с натяжкой списать на проблемы платформы. Оба связаны с (де-)сериализацией, что характерно :). Но и они решились не переписыванием со скалы на раст, а просто заменой библиотеки и небольшими оптимизациями кода.
А что до синтетических бенчмарков: в самом популярном сравнении HTTP серверов akka-http стабильно болтается около 190-го места. При этом в проде работает нормально и кушать не просит. Если что-то и тормозит, то в коде приложения, а не фреймворка. Современное состояние индустрии таково, что фреймворк со дна бенчмарков может держать внушительную нагрузку! И это не говоря о дешёвом железе.
Поэтому в моих глазах топ причин плохого перфоманса выглядит так:
1. баг в коде;
2. ошибка конфигурации;
3. ошибка дизайна системы;
4. ... программист обосрался где-то ещё ...
5. медленный рантайм языка.
Или в руби всё настолько плохо с производительностью, что даже скала по сравнению с ней летает? Или программистам комфортно списывать свои оплошности на «медленный язык»?
Длинный блогпост с подробным объяснением тайп констрейнтов в скале. От паттернов использования до собственной реализации.
Как работают эти магические
https://blog.bruchez.name/posts/generalized-type-constraints-in-scala/
Как работают эти магические
<:<, =:=, и почему недостаточно обычных тайп баундов. Для меня при изучении скалы это долгое время был один из самых непонятных вопросов.https://blog.bruchez.name/posts/generalized-type-constraints-in-scala/
Erik’s Ponderings
Generalized type constraints in Scala (without a PhD)
Introduction
Forwarded from PONV Daily (Igal Tabachnik)
Габриель Вольпе пишет новую книгу про функциональную архитектуру на Скала 3 https://twitter.com/volpegabriel87/status/1448241688342892545
Ещё полгода назад в sbt появилась возможность прописывать схему версионирования зависимостей. Это помогает системе сборке сигнализировать о бинарной несовместимости зная правила версионирования конкретной библиотеки, то есть с меньшим количеством ложных срабатываний. Подробнее об этом написано здесь.
Для меня стало открытием, что версионирование библиотек в скала-экосистеме — это вовсе не кривой semver, а другие схемы со своей формализацией. Так, стандартная библиотека версионируется по PVP:
А другие библиотеки, например, проекты Typelevel, используют early-semver. В нём первая цифра увеличивается только при изменениях, ломающих бинарную совместимость. Если же major=0, то бинарную совместимость можно ломать при каждом минорном релизе. Собственно поэтому версия circe до сих пор начинается с нуля :) И рекомендацией для библиотек является как раз использование early-semver.
Для меня стало открытием, что версионирование библиотек в скала-экосистеме — это вовсе не кривой semver, а другие схемы со своей формализацией. Так, стандартная библиотека версионируется по PVP:
epoch.major.minor. В отличие от semver в нём не надо увеличивать первую цифру при каждом ломающем изменении: её можно инкрементить на усмотрение автора о важности обновления.А другие библиотеки, например, проекты Typelevel, используют early-semver. В нём первая цифра увеличивается только при изменениях, ломающих бинарную совместимость. Если же major=0, то бинарную совместимость можно ломать при каждом минорном релизе. Собственно поэтому версия circe до сих пор начинается с нуля :) И рекомендацией для библиотек является как раз использование early-semver.
В DI-фреймворке под названием ZIO можно автовайрить зависимости с помощью zio-magic, который ещё и встроят в версию 2.0. Код выглядит примерно так:
А дальше макрос на этапе компиляции выстроит граф зависимостей по входящим типам слоёв. Но частенько в приложении бывают классы, принимающие зависимости одного и того же типа. Допустим, grpc-клиенты к микросервисам
Но мне это не нравится, потому что приходится внедрять куски ручного построения графа зависимостей в плоский список слоёв.
Так как вайринг происходит во время компиляции, различать зависимости нужно на уровне типов. Сразу пришла идея использовать тайп-теги: пометить слой зависимости каким-то тегом, и такой же тип затребовать в слое-получателе. Собственно, после некоторого пердолинга с компилятором было написано такое вот поделие: https://gist.github.com/poslegm/f252994a15453457e64d6498249928f3.
И с ним можно писать уже так:
К сожалению не получилось придумать реализацию
Зато теперь все зависимости укладываются в один плоский список, а zio-magic ругнётся, если не будет протегированной зависимости к какому-то слою.
ZLayer.fromMagic[Env](
UsersRepository.live,
ClientsRepository.live,
RegistrationService.live
)
А дальше макрос на этапе компиляции выстроит граф зависимостей по входящим типам слоёв. Но частенько в приложении бывают классы, принимающие зависимости одного и того же типа. Допустим, grpc-клиенты к микросервисам
BillingClient и NotificatorClient принимают в зависимости GrpcConfig. Содержимое этих конфигов очевидно разное, но на уровне типов этого не видно, поэтому нельзя понять, какой экземляр конфига к кому относится. Можно решить это ручным вайрингом:ZLayer.fromMagic[Env](
Grpc.live,
Config.loadLayer[GrpcConfig]("billing") ++ ZLayer.environment[Has[Grpc]] >>> BillingClient.live,
Config.loadLayer[GrpcConfig]("notificator") ++ ZLayer.environment[Has[Grpc]] >>> NotificatorClient.live
)
Но мне это не нравится, потому что приходится внедрять куски ручного построения графа зависимостей в плоский список слоёв.
Так как вайринг происходит во время компиляции, различать зависимости нужно на уровне типов. Сразу пришла идея использовать тайп-теги: пометить слой зависимости каким-то тегом, и такой же тип затребовать в слое-получателе. Собственно, после некоторого пердолинга с компилятором было написано такое вот поделие: https://gist.github.com/poslegm/f252994a15453457e64d6498249928f3.
И с ним можно писать уже так:
ZLayer.fromMagic[Env](
Grpc.live,
Config.loadLayer[GrpcConfig]("billing").tagged[BillingClient.Service],
Config.loadLayer[GrpcConfig]("notificator").tagged[NotificatorClient.Service],
BillingClient.live.requireTagged[GrpcConfig, BillingClient.Service, Has[Grpc]],
NotificatorClient.live.requireTagged[GrpcConfig, NotificatorClient.Service, Has[Grpc]]
)
К сожалению не получилось придумать реализацию
requireTagged, в которой не надо явно указывать компилятору тип "остатка" требуемых слоёв, кроме протегированного.Зато теперь все зависимости укладываются в один плоский список, а zio-magic ругнётся, если не будет протегированной зависимости к какому-то слою.
Совсем недавно скала-сообщество бурлило из-за внезапно обнаруженного отсутствия forward compatibility в Scala 3. Штука и правда неприятная, но, на мой взгляд, скалисты излишне всё драматизировали. На то они и скалисты!
Подробно о проблеме можно почитать тут.
А пару часов назад на форуме разработчиков языка появился план по исправлению этого неудобства — https://contributors.scala-lang.org/t/improving-scala-3-forward-compatibility/5298
TL;DR: будет `-target`, как в джаве.
Подробно о проблеме можно почитать тут.
А пару часов назад на форуме разработчиков языка появился план по исправлению этого неудобства — https://contributors.scala-lang.org/t/improving-scala-3-forward-compatibility/5298
TL;DR: будет `-target`, как в джаве.
Адам Фрейзер (кор-контрибутор ZIO) сделал доклад с очередным набросом на скалу-как-хаскель, дескать, теорию категорий надо знать. Мне понятно, почему Ziverge атакует «академическое» ФП с точки зрения маркетинга своей экосистемы. В этом ключе разумно давить на неофитов стереотипом о сложной математике, без которой в библиотеках конкурента не разобраться.
Но только на практике я программировал в функциональном стиле на Scala до появления ZIO, не зная при этом теорию категорий. Не горжусь своим невежеством в этом вопросе, но вэлью делать оно не мешает. Для программиста все эти монады, стрелки Клейсли и контравариантные функторы в коде быстро превращаются в паттерны со странными названиями. И их изучение мало отличается от изучения синглтонов, визиторов и абстрактных фабрик.
Tagless Final на скале мне неудобен скорее из-за упора на ад-хок полиморфизм, который заставляет учить слишком много тайпклассов. Правда для этого нужна хорошая память, а не математический аппарат :)
Но только на практике я программировал в функциональном стиле на Scala до появления ZIO, не зная при этом теорию категорий. Не горжусь своим невежеством в этом вопросе, но вэлью делать оно не мешает. Для программиста все эти монады, стрелки Клейсли и контравариантные функторы в коде быстро превращаются в паттерны со странными названиями. И их изучение мало отличается от изучения синглтонов, визиторов и абстрактных фабрик.
Tagless Final на скале мне неудобен скорее из-за упора на ад-хок полиморфизм, который заставляет учить слишком много тайпклассов. Правда для этого нужна хорошая память, а не математический аппарат :)