Comments 33
Я правильно понимаю, что это работает только для валидации, которая не требует какого-то внешнего контекста? Вот например если я хочу Email проверять не только синтаксически, но ещё и на тему отсутствия в блеклисте в БД.
Второй вопрос - в сценарии с десериализацией DTO, состоящего из name/email/age - правильно понимаю, что ошибка десериализации будет содержать только первую ошибку? Т.е. если email и age оба некорректные, то на email оно упадёт, а на age даже не посмотрит?
Я полагаю нет смысла лезть в БД с синтаксически неверным email. Т.е. для проверки отсутствия почты в блеклисте уже работаем с уточненным типом
Email
В данном случае видно, что декодер возвращает тип
Either
, что по умолчанию подразумевает Fail fast (падаем при первой ошибке). А для Fail slow, к примеру, может использоваться cats.Validated
Для этого вы можете использовать паттерн Smart Constructor (https://medium.com/@supermanue/smart-constructors-in-scala-fa5a03e25326) совместно с Refined
Кто о чём, а Дедфуд о завтипах.
Пример ручного написания рантайм-валидации как демонстрация нарушения SRP мне кажется притянутым за уши – достаточно вынести валидацию в отдельный метод.
Если я правильно понял, то предложенная библиотека сочетает в себе генерацию валидаторов и их использование и для генерации рантайм-бойлерплейта, и для проверки доступных литералов во время компиляции.
Первый вопрос – это вариация вопроса 0xd34df00d, который в общем случае сведется к зависимым типам. А именно: есть ли у нас, например, возможнасть имея два NonNegative в компайл-тайме получать NonNegative в результате их сложения, но не получать в результате разности? Получать в результате разности только если первое значение больше/равно второго?
Теперь к разделу про взаимодействие с библиотеками. (Побуду занудой, пример, похоже не о «взаимодеиствии с библиотеками вообще», а о «взаимодействии с библиотеками, зависящими от refined» – это немного другое). Вопрос второй – по «case class Foo».
Скалу я не знаю, но знаю, что case-классы в ней – это реализация ADT плюс некоторые особенности. В данном примере кейс-класс с единственным конструктором используется из-за каких-то дополнительных особенноестей? Можно ли использовать просто value-классы или обычные мутабельные классы?
Сам я больше работаю с haxe, поэтому приведу встречные примеры, присущие этому языку. Во-первых в языке есть абстрактные типы – компайл-тайм абстрацкии над другими типами с zero-cost оверхэдом, которые среди прочего позволяют писать рантайм-валидации в теле конструтора. После компиляции тело конструктора/методов/операторов по желанию либо заинлайнятся, либо вынесутся в статический метод. Кроме того, над абстрактами можно определять операции над любыми комбинациями с другими типами. То есть в примере с NonNegative мы легко можем определить операцию сложения с гарантиями компилятора, но для вычетания – только сгенерировать рантайм-проверки. Так как зависимых типов тут нет.
Алгебраические типы в haxe тоже есть, но с абстрактами они никак не связаны и могут использоваться с ними в сочетании в любых комбинациях.
Кроме того, у haxe есть удобный апи для метапрограммирования, который позволяет делать описанное в статье. В качестве примера могу привести коллекцию библиотек haxetink.github.io.
Добавить перегруженные методы для refined типа можно, через обычные extension methods, сильно специфического для Refined ничего в этом плане нет, кроме "поднятия" обернутого типа
Зависимые типы в Scala есть, поэтому разницу двух NonNegatives на этапе компиляции сделать можно, если это литералы (значения известны также на этапе компиляции)
Refined это сторонняя библиотека и в плане использования типов ничем не отличается от любых других типов, будь то в кейс или обычных классах и полях
Каким образом? Вот я ввел с консоли два NonNegative, как ваш код вернет NonNegative для x - y?
Какой именно проверки? Есть пример на каком-то реальном языке? В Scala тоже можно потребовать какой-нибудь имлиситный параметр и создавать его в if'e :)
Тут ключевой момент вот в чём. Его тип может зависеть от соответствующих значений? Ну, чтобы тип имплиситного параметра указывал на то, что это именно свидетельство того, что x меньше y?
Я думаю, элегантного решения ни во 2, ни в 3 Scala действительно нет
Из зала подсказали :)
https://scastie.scala-lang.org/jDwimqoSTHqoAk1D6wse2g
Что такое
x.type
?
тип переменной х
Олсо, поменял местами
x
иy
вwhen
, код всё ещё компилируется. Ерунда какая-то.
разумеется, компилируется, ведь Lte это просто кастомный класс в сниппете. Он скомпилируется даже если назовете его Unicorn. Могу переложить в пакет Prelude, если так будет лучше :)
Помнит, x.type != Int, это тип конкретной переменной x, которая в скоупе:
scala> val x = 15
val x: Int = 15
scala> val y: x.type = x
val y: x.type = 15
scala> val y: x.type = 15
^
error: type mismatch;
found : Int(15)
required: x.type
Для этого его достаточно положить в [стандартную] библиотеку, в Idris'е же оно не из вакуума появляется
А для Int'ов? Я посмотрел реализацию LTE в Idris и, кажется, придумал как сделать то же самое в Scala 3 (через https://dotty.epfl.ch/docs/reference/new-types/match-types.html)
Но тогда мне придется писать еще свою реализацию Nat, а я, право, ленивый
https://scastie.scala-lang.org/yqOOLQ3aS2SsQsDlsKsjVQ
переполнение стека исправлять не буду, я обошелся Scala 2 :)
Я извиняюсь, что лезу не в свой огород (не пишу на Scala), но разве для подобных ограничений не существует подход с DbC (контрактным программированием)?
Он менее элегантен, однако констреинты не будут проверяться в рантайме, а значит и выше производительность. Плюс подход с пре/пост-кондишенами и инвариантами более явен и ориентирован не на сам тип "Email", а тип в рамках какого-либо скоупа/контекста (например "String" внутри "User").
Но даже не взирая на различия — задачи идентичные: Специализация примитивных типов.
Это своего рода облегченная версия Unit Types из F#.
Refined типы в Scala