Чем точнее типы, тем меньше кода и тестов
def getElement[A](vec: Vector[A])(index: Int): Option[A] = {
if(index >= 0 && index < arr.length)
Some(arr(index))
else
None
}
def getElement[A, N](vec: Vector[A] Refined Size[Equal[N]])
(i: Int Refined And[NonNegative, Less[N]]): A =
vec.value(i)
Вывод (inference, widening)
val i: Int Refined Greater[W.`1`.T] = ???
def sqrt(z: Int Refined Greater[W.`0`.T]) = ???
import eu.timepit.refined.auto._
sqrt(i)
Int Refined Greater[W.`1`.T] <:< Int Refined Greater[W.`0`.T]
Возможности из коробки
type PortNumber = Int Refined And[ Not[Less[W.`0`.T]],
Less[W.`65536`.T] ]
type AllowedChar = Char Refined
AnyOf[Digit :: Letter ::
Whitespace :: HNil]
Возможности из коробки 2
type ListOfNonEmptyStrings = List[String] Refined
Forall[NonEmpty]
type MyListOfInts = List[Int] Refined
Tail[Exists[Positive]]
type ListOfSize3[T] = List[T] Refined
Size[Equal[W.`3`.T]]
type ListOfEvenSize[T] = List[T] Refined
Size[Even]
type ListOfSizeGt2[T] = List[T] Refined
MinSize[W.`2`.T]
Возможности из коробки 3
type EMail = String Refined
MatchesRegex[W.`"^[a-zA-Z0-9.!#$%&’*+/=?^_\`{|}~-]+@[a-zA-Z0-9-]+(?:\\\\.[a-zA-Z0-9-]+)*$"`.T]
Применения: circe
type PortNumber = Int Refined
Interval.Closed[_0, W.`65535`.T]
type StringUrl = String Refined Url
case class EndPoint(url: StringUrl, port: PortNumber)
implicit val decodeEndPoint: Decoder[EndPoint] =
Decoder.forProduct2("url", "port")(EndPoint.apply)
Применения: doobie
"throw exception if the refinement fails" in {
secondaryValidationFailedCaught_?(
sql"select -1".query[PositiveInt]
.unique.transact(xa).void.unsafeRunSync
)
}
Применения: slick
class FooBars(tag: Tag) extends Table[Foobar](tag, "FOOBARS")
{
def bar = column[PosInt]("BAR", O.PrimaryKey)
def foo = column[NonEmptyString]("FOO")
def * = (bar, foo) <> (FooBar.tupled, FooBar.unapply)
}
Как работает под капотом?
final class Refined[T, P] private (val value: T)
extends AnyVal
object Refined {
def unsafeApply[T, P](value: T): Refined[T, P] =
new Refined(value)
}
trait Validate[T, P] {
def validate(t: T): Either[String,Refined[T,P]]
}
final case class IsZero()
implicit def isZeroValidate[T](
implicit n: Numeric[T]
): Validate[T, IsZero] = (t: T) => {
if (n.zero == t)
Right(Refined.unsafeApply(t))
else
Left("IsZero failed for " + t)
}
final case class IsEqual[U](u: U)
implicit def equalValidate[T, U](
implicit w: Witness.Aux[U]
): Validate[T, IsEqual[U]] = (t: T) => {
if (w.value == t)
Right(Refined.unsafeApply(t))
else
Left("IsEqual(" + w.value + ") failed for " + t)
}
shapeless.Witness.`0`.T
def selectDynamic(tpeSelector: String): Any =
macro SingletonTypeMacros.witnessTypeImpl
Комбинаторы And, Or, ...
final case class And[A,B]()
final case class Or[A,B]()
final case class Not[A]()
implicit def andValidate[T, A, B](
implicit va: Validate[T, A], vb: Validate[T, B]
): Validate[T, And[A, B]] = (t: T) => {
(va.validate(t), vb.validate(t)) match {
case (Right(_), Right(_)) =>
Right(Refined.unsafeApply(t))
case (Left(e), _) => Left(e)
case (_, Left(e)) => Left(e)
}
}
Вывод (inference, widening)
case class Inference[P, C](isValid: Boolean)
type ==>[A, B] = Inference[A, B]
implicit def greaterInferenceNat[A <: Nat, B <: Nat](
implicit ta: ToInt[A], tb: ToInt[B]
): Greater[A] ==> Greater[B] =
Inference(ta() > tb(),
s"greaterInferenceNat(${ta()}, ${tb()})")
implicit def autoInfer(implicit ir: A ==> B) =
macro InferMacro.impl[F, T, A, B]
def impl = macro {
val inference = eval(ir)
if (!inference.isValid) {
abort(invalidInference(...))
}
unsafeRewrap(rt)
}
def max(x: Int, y: Int): { v: Int => v >= x && v >= y } =
if (x > y) x else y
type NonNegative = { v: Int => v >= 0}
def sqrt(z: NonNegative): Double =
scala.math.sqrt(z.toDouble)
val u: Int = ???
sqrt(max(0,u))
- See the presentation at GitHub
- Mail to zhizhelev at Primetalk.