Lil Functor
1.04K subscribers
57 photos
1 file
180 links
Pure functional and composable channel

Чат: https://t.me/+L-xb_m_4lnY3Y2Fi
Download Telegram
Не сразу заметно, но в документации третьей скалы работает аналог Hoogle. Hoogle — это поисковый движок по библиотекам на Haskell, умеющий искать по сигнатуре функции. В документации Scala 3 тоже можно нажать на лупу и ввести, например, Option[A] => A => Boolean: поиск найдёт функцию contains. Правда, далеко не на первом месте.

Поиск сделан на библиотеке Inquire. Как подключить её в свой скаладок, не понятно. Ещё в Hoogle проиндексированы все хаскельные библиотеки, а Inquire умеет искать только по текущей.

Пока выглядит скорее как игрушка, но вот если бы в IDEA или Metals появилось аналогичное решение с поиском по сигнатурам в проекте и его зависимостях, было бы прямо круто.
От чтения тайпскриптовых ФП-библиотек (раз, два) складывается впечатление, что они проходят тот же этап, что и скалисты несколько лет назад, когда тащили всё подряд из хаскеля. Только в тайпскрипте это выглядит как будто бы ещё хуже: один только заменитель do-нотации чего стоит. В исходниках ещё много вещей, которые интересны своей изобретательностью в обходе ограничений компилятора, но болезнены в использовании.

Похоже, что "переводы" хаскеля — это проклятие всех языков с развитой системой типов, а идиоматичный инструментарий для ФП появляется только спустя годы экспериментов.

Система типов в TS при этом передовая для промышленного языка: условные типы, типы-литералы даже с поддержкой шаблонов, flow typing, пересечения и объединения типов. Часть этих фич только появилась в Scala 3, а в тайпскрипте существует уже несколько лет. Возможно, тайплевельные истории там не летят из-за интеропа с JS, который гораздо более неприятный, чем интероп с джавой в Scala-мире.
Есть один супер-интересный канал с разбором пейперов по распределённым системам, на котором несправедливо мало подписчиков.

Всем советую подписаться, там о больших и сложных вещах понятным языком рассказывают. Сегодня вот вышел пост о FoundationDB.

https://t.me/shark_in_it
Удобнейший плагин для релизов артефактов в Maven Central sbt-ci-release переехал в организацию sbt на гитхабе и похоже стал официально рекомендованным способом автоматизации релизов.

Это здорово, потому что релизить что-то в 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
Посмотрел на Ruby Russia кейноут создателя языка. Там он всерёз говорит об оптимизации языка под микробенчмарки, потому что программисты слишком серьёзно к ним относятся, и выбирают языки, которые обгоняют Ruby. То есть буквально не из-за того, что ускорение рантайма принесёт какую-то ценность, а ради увеличения привлекательности языка. Дальше рассказывал про целый роадмап повышения перфоманса Ruby: многоуровневый JIT, гранты контрибуторам за микрооптимизации. И всё это под соусом того, что «медлительность» вредит маркетингу.

И вот не понимаю: программисты действительно столько внимания уделяют синтетическим бенчмаркам? Почему?

Конечно, есть критичные к перфомансу области разработки, но они уже заняты специфичными языками: Rust, C++. На Ruby пишутся в основном веб-бэкенды, а в них, чтобы упереться в производительность *языка*, надо особо постараться. Если в сервисе что-то тормозит, то почти всегда накосячил программист, а не рантайм. На своей практике припоминаю только два случая, когда тормоза на высоких нагрузках можно было с натяжкой списать на проблемы платформы. Оба связаны с (де-)сериализацией, что характерно :). Но и они решились не переписыванием со скалы на раст, а просто заменой библиотеки и небольшими оптимизациями кода.

А что до синтетических бенчмарков: в самом популярном сравнении HTTP серверов akka-http стабильно болтается около 190-го места. При этом в проде работает нормально и кушать не просит. Если что-то и тормозит, то в коде приложения, а не фреймворка. Современное состояние индустрии таково, что фреймворк со дна бенчмарков может держать внушительную нагрузку! И это не говоря о дешёвом железе.

Поэтому в моих глазах топ причин плохого перфоманса выглядит так:

1. баг в коде;
2. ошибка конфигурации;
3. ошибка дизайна системы;
4. ... программист обосрался где-то ещё ...
5. медленный рантайм языка.

Или в руби всё настолько плохо с производительностью, что даже скала по сравнению с ней летает? Или программистам комфортно списывать свои оплошности на «медленный язык»?
Длинный блогпост с подробным объяснением тайп констрейнтов в скале. От паттернов использования до собственной реализации.

Как работают эти магические <:<, =:=, и почему недостаточно обычных тайп баундов. Для меня при изучении скалы это долгое время был один из самых непонятных вопросов.

https://blog.bruchez.name/posts/generalized-type-constraints-in-scala/
Ещё полгода назад в sbt появилась возможность прописывать схему версионирования зависимостей. Это помогает системе сборке сигнализировать о бинарной несовместимости зная правила версионирования конкретной библиотеки, то есть с меньшим количеством ложных срабатываний. Подробнее об этом написано здесь.

Для меня стало открытием, что версионирование библиотек в скала-экосистеме — это вовсе не кривой semver, а другие схемы со своей формализацией. Так, стандартная библиотека версионируется по PVP: epoch.major.minor. В отличие от semver в нём не надо увеличивать первую цифру при каждом ломающем изменении: её можно инкрементить на усмотрение автора о важности обновления.

А другие библиотеки, например, проекты Typelevel, используют early-semver. В нём первая цифра увеличивается только при изменениях, ломающих бинарную совместимость. Если же major=0, то бинарную совместимость можно ломать при каждом минорном релизе. Собственно поэтому версия circe до сих пор начинается с нуля :) И рекомендацией для библиотек является как раз использование early-semver.
В DI-фреймворке под названием ZIO можно автовайрить зависимости с помощью zio-magic, который ещё и встроят в версию 2.0. Код выглядит примерно так:

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`, как в джаве.
Адам Фрейзер (кор-контрибутор ZIO) сделал доклад с очередным набросом на скалу-как-хаскель, дескать, теорию категорий надо знать. Мне понятно, почему Ziverge атакует «академическое» ФП с точки зрения маркетинга своей экосистемы. В этом ключе разумно давить на неофитов стереотипом о сложной математике, без которой в библиотеках конкурента не разобраться.

Но только на практике я программировал в функциональном стиле на Scala до появления ZIO, не зная при этом теорию категорий. Не горжусь своим невежеством в этом вопросе, но вэлью делать оно не мешает. Для программиста все эти монады, стрелки Клейсли и контравариантные функторы в коде быстро превращаются в паттерны со странными названиями. И их изучение мало отличается от изучения синглтонов, визиторов и абстрактных фабрик.

Tagless Final на скале мне неудобен скорее из-за упора на ад-хок полиморфизм, который заставляет учить слишком много тайпклассов. Правда для этого нужна хорошая память, а не математический аппарат :)
Новая микро-драма в Scala-сообществе: Роб Норрис, мейнтенер библиотеки для работы с SQL doobie, удалил интеграцию с другой библиотекой для SQL quill, потому что та вошла под крыло компании Ziverge, с которой у Норриса личный конфликт.

Поддерживать интеграции нескольких независимых библиотек в общем случае большая нагрузка на мейнтейнеров из-за необходимости «дружить» API, изменяющиеся без согласования друг с другом. То, что такая интеграция находилась непосредственно в doobie, а не поддерживалась в виде опосредованной библиотеки, накладывало определённую нагрузку и риски на мейнтейнеров. Поэтому, на мой взгляд, они в праве отказываться от поддержки таких решений по уйме причин: расхождения API, сложность работы с зависимостями, нехватка личного времени и т.д.

Но Норрис решил в один момент разбомбить Воронеж, то есть конечных пользователей, использующих эту интеграцию в проде. Что можно было сделать лучше?
1. Вынести интеграцию в отдельный репозиторий на гитхабе, сохранив неймспейс и название джарника. Если не хочется делать это самостоятельно, поставить issue на авторов quill с обозначенными сроками отказа от поддержки.
2. Задепрекейтить код интеграции, обозначив версию, в которой он будет удалён.
3. Изначально держать коннекторы в отдельных репозиториях и версионировать их независимо от основной библиотеки. В этом плане ZIO более дальновидно.

Собственно, сообщество подсуетилось и спасло эти аж ~200 строк кода интеграции. Которые, впрочем, можно и в свой проект скопипастить.

К слову, ценность этой интеграции для меня под вопросом. Писать код на DSL квилла и запускать на транзакторе из doobie не особо здорово, потому что doobie гвоздями прибит к jdbc. Хотелось бы наоборот, писать plain SQL в духе doobie и запускать их на асинхронном драйвере к постгре или другой базе, которые как раз поддерживаются в quill.

Эмоциональный выпад Норриса был замечен в высоких кабинетах Лозанны, и профессор Одерски лично отметил поведение деструктивным. В ответ на форуме контрибуторов Scala была созданна тема, в которой сообщество было обвинено в токсичности к меньшинствам, а Одерски чуть ли не в причастности к убийствам. Но об SQL и поддержке библиотек там ни слова, поэтому читать стоит только если очень хочется насладиться дивным новым миром.
Вот и пригодилась компиляция Scala → JavaScript. Только не для фронтенда.

Daniel Spiewak в концепте serverless-фреймворка предлагает компилировать код на Scala в JS, чтобы запускать его на движке V8. Таким образом получится избежать проблемы холодного старта JVM для короткоживущих функций. V8 не надо прогревать, а код для него можно писать всё на той же Scala. Не зря ведь scala.js делали и библиотеки под него кроссбилдили!

Понятно, что scala-native слишком далека от готовности к проду, но интересно, почему решили не использовать Native Image в GraalVM. Решение с js в любом случае оригинальное и может быть из него что-то вырастет.

Сам концепт: https://gist.github.com/djspiewak/37a4ea0d7a5237144ec8b56a76ed080d
Прототип библиотеки: https://github.com/typelevel/feral
Текстовая версия недавнего доклада Li Haoyi (автора книги Hands On Scala и множества библиотек и инструментов) о применении Scala в DataBricks. Наконец-то нормальная саццесс-стори вместо набивших оскомину драм в сообществе. Там и про управление зависимостями, и про сборку с CI, и про скрипты на библиотеках самого Li.

https://databricks.com/blog/2021/12/03/scala-at-scale-at-databricks.html

we have been able to scale our Scala-using engineering teams without issue and reap the benefits of using Scala as a lingua franca across the organization
Дорогие подписчики, поздравляю с новым годом!

Желаю вам в 2022 карьерных достижений, академических изысканий и жадного ума 🚀
C4 Model

Меня сильно заинтересовали способы грамотной визуализации архитектуры программных систем, потому что диаграмы уровня [kafka] → [scala] → [postgresql] порядком поднадоели. Хочется выработать максимально информативный, но в тоже время удобный способ рисовать то, что мы проектируем.

Из того, что нашёл, больше всего понравился фреймворк C4model. Но зачем вообще нужен целый фреймворк для визуализации? Без него каждый раз приходится импровизировать, что нарисовать на диаграмме. А нарисовать хочется многое: юзер-стори, технологии, карту микросервисов и хранилищ, технические ограничения... В итоге получается либо картинка с хаотичным набором стрелочек и квадратиков с перемешанными абстракциями «всё в одном», либо фрустрация и рисование примитивной схемы а-ля «ну тут сервис, тут база, всё понятно вроде».

C4 предлагает решение через разделение уровней детализации: нарисовать несколько схем от взляда на систему с высоты птичьего полёта до всё более подробных представлений отдельных процессов. Cлоёв ровно четыре, и их описание можно прочитать на сайте фреймворка. У себя на сайте я оставил краткий конспект (тут и так слишком много слов для телеграма).

Мне нравится подход C4, но с парой ремарок:

→ Я бы сократил C4 до C3. Визуализация связей внутри кода приложения кажется избыточной: если все сервисы пишутся по единым паттернам в неком подобии гексагональной архитектуры, то и связи между модулями будут типовыми. Лучше инвестировать в выработку организационных стандартов кодирования, чем в рисование/генерацию UML.
→ В микросервисных реалиях одно приложение содержит в себе совсем немного компонент, поэтому на третьем уровне вместо статической диаграммы внутренностей отдельного приложения лучше нарисовать основные процессы, которые оно выполняет. Для этого хорошо подходит диаграмма последовательности из PlantUML.
→ Описание слоёв не является догмой и должно адаптироваться под нужды организации или команды.

Напишите в комментарии, что вы у себя используете для визуализации/документирования разработок?
Uber публично задокументировал свой подход к организации микросервисов — Domain-Oriented Microservice Architecture (DOMA). Он решает проблему колоссального возрастания сложности системы у организаций с несколькими тысячями микросервисов. Для организаций меньшего масштаба весь подход будет избыточен, а вот идею «слоёного дизайна» стоит почерпнуть.

Принцип заключается в разделении системы на иерархию слоёв. В самом низу лежат инфраструктурные сервисы, наверху — реализация продуктовых фич и представления данных для фронтенда. Прямые зависимости между сервисами допустимы только внутри слоя, а в соседний слой можно сходить через единую точку входа. Причём соседний — это нижний. Слой может зависеть только от слоя, который находится под ним.

Например, продукт с оплатой услуг раскладывается на три слоя (сетевую инфраструктуру и гейтвеи фронтенда для простоты отброшу):

* Биллинговая система, оперирующая платёжками, реквизитами юр. лиц, данными банковских карт, чеками и тому подобным, на нижнем слое иерархии. Она может быть использована в принципиально разных продуктах, главное, чтобы они забирали деньги у пользователей;
* Сервисы ценообразования и механики применения услуг в конкретных продуктовых доменах. На этом слое может быть несколько сервисов, если у направлений бизнеса отличается логика тарификации;
* Продуктовые сервисы, реализующие юзер-стори.

Отчётливо видно движение от универсальной функциональности к специфичной и от высокой критичности к низкой. Развалится биллинг — пострадают все, а если продуктовый сервис — только его фича.

Когда пользователь активирует платную услугу, продуктовый слой обращается только к соседу ниже запросами вида «активируй услугу X пользователю Y». Он не работает с биллингом и тем более не реализует бизнес-логику в терминах биллинговых систем. Нижний слой тоже не обращается к продуктовому и не знает его моделей.

Важно не использование «слоёв» в каноничном уберовском понимании, а наличие иерархии в организации системы. Без неё сложность растёт неконтролируемо: домены переплетаются, экспертиза размазывается между командами, граф зависимостей сервисов превращается в ёжика. Выделить правильную иерархию без лишних абстракций — сложная архитектурная задача, но даже несовершенная иерархия лучше её отсутствия.
sbt версий 1.6.0 и 1.6.1 был с багом: при ошибке инициализации фреймворка для юнит тестов он успешно завершал билд. Это случайно заметили на коммьюнити-билде Scala 3: там у десятка проектов не отрабатывала даже компиляция тестов, а билды были зелёными.

Такие молчаливые баги я не люблю больше всех. Догадаться, что сломался CI при зелёных чеках, можно разве что по подозрительно быстрому времени завершения. Да и то, пока кто-то на него посмотрит, уже случится пара (десятков) выкладок в прод.

Пофикшено в 1.6.2.
Kit Langton, автор макроса для автовайринга ZLayer, отметился в любопытном пропозале на форуме разработчиков компилятора Scala. В пропозале обсуждается синтаксический сахар для аппликативной композиции по аналогии с for-comprehension для монадок. Если сильно упрощать смысл аппликативных вычислений до наиболее частого практического применения — это вычисление нескольких эффектов параллельно и сбор результатов в одном месте (академики, не ругайтесь).

Сейчас в популярных библиотеках параллельные вычисления описываются вспомогательными функциями, и это не особо удобно в реальности, когда собрать надо с десяток значений:

ZIO.mapParN(fetchName, fetchAge)((name, age) => User(name, age))

А программисты хотят синтаксического сахарка, как если бы вычисления были последовательными:

afor {
name <- fetchName
age <- fetchAge
} yield User(name, age)

В предложенном варианте меньше бросается в глаза, что вычисления побегут в параллель, но читается он гораздо лучше. В Haskell такое расширение компилятора уже реализовано и выглядит довольно симпатично.

Так вот, Kit написал работающий PoC макроса, который даёт такой синтаксис без доработок компилятора. Пока только для ZIO.

par {
for {
name <- fetchName
age <- fetchAge
} yield User(name, age)
}

Макрос строит граф вычислений из обёрнутого for-comprehension, топологически его сортирует и параллелит всё, что можно. Утилита уже сейчас выглядит привлекательно, и может быть её втащат в ZIO, как это уже было с zio-magic.

https://github.com/kitlangton/parallel-for