Получи случайную криптовалюту за регистрацию!

Lil Functor

Логотип телеграм канала @lilfunctor — Lil Functor L
Логотип телеграм канала @lilfunctor — Lil Functor
Адрес канала: @lilfunctor
Категории: Технологии
Язык: Русский
Страна: Россия
Количество подписчиков: 930
Описание канала:

Pure functional and composable channel
Чат: https://t.me/ L-xb_m_4lnY3Y2Fi

Рейтинги и Отзывы

3.00

2 отзыва

Оценить канал lilfunctor и оставить отзыв — могут только зарегестрированные пользователи. Все отзывы проходят модерацию.

5 звезд

1

4 звезд

0

3 звезд

0

2 звезд

0

1 звезд

1


Последние сообщения 3

2021-07-07 19:47:01 Нашёл библиотечку OCDQuery с генерацией запросов на doobie по пользовательским моделям данных. Сама библиотека выглядит заброшенной, а вот в документации лежит сокровище — понятное описание проблематики и реализации паттерна Higher Kinded Data для моделей сущностей из БД.

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

Количество тайп-параметров и их комбинаций можно делать любое в зависимости от сценариев использования данных. Главное не получить комбинаторный взрыв :)
1.8K views16:47
Открыть/Комментировать
2021-07-04 20:47:01 Напоминаю, что Яндекс.Вертикали нанимают скалистов!

Мы делаем три площадки объявлений: auto.ru, Я.Недвижимость и Я.Объявления. Подробный рассказ о RSU, техническом стеке и стульях Herman Miller можно прочитать в вакансии: https://telegra.ph/Vakansiya-behkend-razrabotchika-na-Scala-03-04

Со своей колокольни могу добавить, что вам особенно нужно в Вертикали, если:
– вы планируете переход из разработки в управление. В этом случае у вас будет синергия с постоянной потребностью лидов разработки в Вертикалях;
– вы вэлью-челик с интересом к продукту и сильной технической базой. Вам будет комфортно и не скучно работать в продуктовых командах;
– вы хотите перейти из джавы в скалу. У нас есть опыт адаптации джавистов, а размер компании обеспечит плавный переход;
– вы работаете в стартапе и хотите попробовать бигтех на вкус. Вертикали достаточно небольшой юнит по меркам Яндекса, поэтому шока от перехода в корпорацию почти не будет. А вот все плюшки — будут.

Резюме и вопросы присылайте в лс -> @poslegm

И самое главное: нельзя. игнорировать. тотал.
1.8K views17:47
Открыть/Комментировать
2021-06-30 20:37:57 Поигрался с match types в Scala 3. Сильно хотел использовать их для определения зависимости возвращаемого типа функции от типа аргумента, но не прокатило. Не понравилось, что матчинг при использовании типа происходит в рантайме, поэтому можно словить исключение…
1.2K views17:37
Открыть/Комментировать
2021-06-30 19:48:25 Поигрался с match types в Scala 3. Сильно хотел использовать их для определения зависимости возвращаемого типа функции от типа аргумента, но не прокатило. Не понравилось, что матчинг при использовании типа происходит в рантайме, поэтому можно словить исключение 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

Несмотря на то, что эта штука работает, выглядит она не как нативная языковая
конструкция, а как насилие над компилятором.
1.1K views16:48
Открыть/Комментировать
2021-06-24 13:53:35 Задача (де)сериализации структур в json решается тремя способами:

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 его обгоняет. Только вместо макросов там кодеки просто кодогенерируются перед сборкой проекта. Языки разные, а проблемы у всех одинаковые :)
430 views10:53
Открыть/Комментировать
2021-06-20 17:28:27 Ковырялся на работе с тапиром, не понравилось. tapir — это популярный в Scala-мире DSL для описания HTTP эндпоинтов. DSL интерпретируется в “настоящие” эндпоинты на одном из четырёх доступных веб-серверов и автоматически генерирует спецификацию в OpenAPI. Собственно, ради автогенерации сваггера мы и затащили его в один REST-сервис. Руками OpenAPI описывать всё-таки нудно и ненадёжно, а gRPC не подходил по требованиям к сервису.

Основную боль причинила аутентификация: она дырявая и неудобная. Тапир сначала парсит метод и путь запроса, а только потом проверяет наличие Auth-заголовка. Значит злоумышленник может пореверс-инжинирить контракт, получая осмысленные 400 и 404 вместо 401. Если написать код аутентификации так, как предлагает документация, сервер ещё и тело запроса парсит . Уязвимость с телом я обошёл костылём, а вот чтобы заткнуть дыру с парсингом пути, пришлось кинуть PR (надеюсь, примут). Правда код из документации всё равно останется дырявым by design, извольте писать костыли.

Следующая боль — маппинг ошибок. Тапир заставляет маппить эксепшены в HTTP-коды один к одному. При этом компиляторных гарантий полноты маппинга нет… Естественно, всё многообразие ошибок бизнес-логики 1-к-1 на HTTP-коды не замаппишь, поэтому приходится делать промежуточную ADT с сетевыми ошибками, а уже её маппить в коды.

Но и такой маппинг нельзя красиво встроить в код эндпоинтов, потому что в тапире нет абстракции для middleware! Поэтому всякие маппинги ошибок, логирование и тд приходится вкорячивать либо непосредственно в веб-сервер (если до него дойдёт всё, что хочется залогировать), либо костылить всратыми адхоками.

Спасибо, Господи, за gRPC и GraphQL.
1.4K views14:28
Открыть/Комментировать
2021-06-18 23:15:39 В экосистеме Cats Effect есть утилита Hotswap, предназначенная для управления жизненным циклом заменяемых "на горячую" ресурсов через общий ресурсный скоуп. К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от…
410 views20:15
Открыть/Комментировать
2021-06-18 23:14:54 В экосистеме Cats Effect есть утилита Hotswap, предназначенная для управления жизненным циклом заменяемых "на горячую" ресурсов через общий ресурсный скоуп.

К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от конкуретных попыток свопа, а релиз заменяемого ресурса не отслеживает его использование другими файберами, что легко приводит к утечке.

Для 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 сделал за несколько минут.
414 views20:14
Открыть/Комментировать
2021-06-12 11:28:20 Написал небольшую библиотеку с интеграцией ZIO и MUnit

https://github.com/poslegm/munit-zio

Мне нравится MUnit за его простоту, минималистичность и наглядные сообщения об ошибках. Чего нельзя сказать о «нативном» для ZIO zio-test: это очень сложный фреймворк с кучей ассершенов, незадокументированных методов провайдинга зависимостей и аспектов.

На мой взгляд тесты должны быть простыми, поэтому и захотелось тестировать свои монадки на MUnit.

А ещё вся документация по munit-zio умещается в README, а полной документации по zio-test в принципе не существует.
405 views08:28
Открыть/Комментировать
2021-06-09 11:04:20 Мелочь, а приятно: в третьей скале можно не придумывать имена для контекстных параметров.

def f(fut: Future[Int])(using ExecutionContext): Future[String] =
fut.map(_.toString)

вместо

def f(fut: Future[Int])(implicit ec: ExecutionContext): Future[String] =
fut.map(_.toString)

из второй скалы.

Контекстные параметры очень часто нужны просто чтобы прокинуть их дальше или задать ограничение на тип, поэтому в их именовании нет смысла. Так что с анонимными параметрами и когнитивная нагрузка на придумывание имени пропадает, и пространство имён не засоряется заведомо не используемыми значениями.

Подробнее о контекстных абстракциях хорошо написано в документации.
511 views08:04
Открыть/Комментировать