Custom Inlined Numeric
InlinedNumeric* traits are helpers for creating custom numeric refined types with less boilerplate.
They are good when your domain rule is a numeric bound and you want:
- inline compile-time validation for literals
- runtime validation with
Either[String, X] - reusable, domain-specific numeric types
Import
import refined4s.types.all.*
Choose a Trait
| Trait | Constraint | Use when |
|---|---|---|
InlinedNumericMinMax[A] | minValue <= value <= maxValue | both lower and upper bounds are required |
InlinedNumericMin[A] | minValue <= value | only lower bound is required |
InlinedNumericMax[A] | value <= maxValue | only upper bound is required |
InlinedNumericMinMax
InlinedNumericMinMax is a helper trait for numeric custom refined types that have
both minimum and maximum bounds.
It gives you:
- inclusive range validation (
minValue <= value <= maxValue) - built-in
predicateandinvalidReason - inline validation support for literals with
apply(compile-time), plus runtime validation withfrom
Use it when your type is a numeric value constrained to a closed interval.
How to use
- Define your custom type as
type X = X.Type. XextendsInlinedNumericMinMax[A]fromrefined4s.types.numericorrefined4s.types.all.- Implement
minValueandmaxValueasinline def. - Use
applyfor literals andfromfor runtime values.
Example
type Percent = Percent.Type
object Percent extends InlinedNumericMinMax[Int] {
override inline def minValue: Int = 0
override inline def maxValue: Int = 100
}
the minValue and maxValue have to be defined as inline def so that they can be used at compile-time.
Create with Compile-time Validation
Compile-time Validation (apply)
Percent(0)
// res0: Type = 0
Percent(100)
// res1: Type = 100
Percent(-1)
// Percent(-1)
// ^^^^^^^^^^^
// Invalid value: [-1]. It must be >= 0 && <= 100.
Percent(101)
// Percent(101)
// ^^^^^^^^^^^^
// Invalid value: [101]. It must be >= 0 && <= 100.
Create with Runtime Validation
Runtime Validation (from)
val percentInput1 = 20
// percentInput1: Int = 20
val percentInput2 = 120
// percentInput2: Int = 120
Percent.from(percentInput1)
// res2: Either[String, Type] = Right(value = 20)
Percent.from(percentInput2)
// res3: Either[String, Type] = Left(
// value = "Invalid value: [120]. It must be >= 0 && <= 100."
// )
Functional Runtime Handling
def describePercent(input: Int): String =
Percent.from(input).fold(
error => s"Invalid percent input: $error",
percent => s"Validated percent: ${percent.value}"
)
describePercent(35)
// res4: String = "Validated percent: 35"
describePercent(135)
// res5: String = "Invalid percent input: Invalid value: [135]. It must be >= 0 && <= 100."
Runtime Unsafe Validation (unsafeFrom)
unsafeFrom may throw an exception if the input value is invalid. So it is not recommended to use it.
Percent.unsafeFrom(percentInput2)
// java.lang.IllegalArgumentException: Invalid value: [120]. It must be >= 0 && <= 100.
// at refined4s.RefinedBase.unsafeFrom$$anonfun$1(RefinedBase.scala:27)
// at scala.util.Either.fold(Either.scala:198)
// at refined4s.RefinedBase.unsafeFrom(RefinedBase.scala:27)
// at refined4s.RefinedBase.unsafeFrom$(RefinedBase.scala:7)
// at repl.MdocSession$MdocApp$Percent$.unsafeFrom(custom-inlined-numeric.md:15)
// at repl.MdocSession$MdocApp.$init$$$anonfun$1(custom-inlined-numeric.md:75)
InlinedNumericMin
InlinedNumericMin is a helper trait for numeric custom refined types that have
only a minimum bound.
It gives you:
- lower-bound validation (
minValue <= value) - built-in
predicateandinvalidReason - inline validation support for literals with
apply(compile-time), plus runtime validation withfrom
Use it when your type is a numeric value constrained to a lower-bound inclusive interval.
How to use
- Define your custom type as
type X = X.Type. XextendsInlinedNumericMin[A]fromrefined4s.types.numericorrefined4s.types.all.- Implement
minValueasinline def. - Use
applyfor literals andfromfor runtime values.
Example
type NonNegativeCount = NonNegativeCount.Type
object NonNegativeCount extends InlinedNumericMin[Int] {
override inline def minValue: Int = 0
}
the minValue has to be defined as inline def so that it can be used at compile-time.
Create with Compile-time Validation
Compile-time Validation (apply)
NonNegativeCount(0)
// res6: Type = 0
NonNegativeCount(10)
// res7: Type = 10
NonNegativeCount(-1)
// NonNegativeCount(-1)
// ^^^^^^^^^^^^^^^^^^^^
// Invalid value: [-1]. It must be >= 0.
Create with Runtime Validation
Runtime Validation (from)
val nonNegativeInput1 = 3
// nonNegativeInput1: Int = 3
val nonNegativeInput2 = -3
// nonNegativeInput2: Int = -3
NonNegativeCount.from(nonNegativeInput1)
// res8: Either[String, Type] = Right(value = 3)
NonNegativeCount.from(nonNegativeInput2)
// res9: Either[String, Type] = Left(
// value = "Invalid value: [-3]. It must be >= 0."
// )
Functional Runtime Handling
def describeNonNegativeCount(input: Int): String =
NonNegativeCount.from(input).fold(
error => s"Invalid non-negative count input: $error",
count => s"Validated count: ${count.value}"
)
describeNonNegativeCount(7)
// res10: String = "Validated count: 7"
describeNonNegativeCount(-2)
// res11: String = "Invalid non-negative count input: Invalid value: [-2]. It must be >= 0."
Runtime Unsafe Validation (unsafeFrom)
unsafeFrom may throw an exception if the input value is invalid. So it is not recommended to use it.
NonNegativeCount.unsafeFrom(nonNegativeInput2)
// java.lang.IllegalArgumentException: Invalid value: [-3]. It must be >= 0.
// at refined4s.RefinedBase.unsafeFrom$$anonfun$1(RefinedBase.scala:27)
// at scala.util.Either.fold(Either.scala:198)
// at refined4s.RefinedBase.unsafeFrom(RefinedBase.scala:27)
// at refined4s.RefinedBase.unsafeFrom$(RefinedBase.scala:7)
// at repl.MdocSession$MdocApp$NonNegativeCount$.unsafeFrom(custom-inlined-numeric.md:86)
// at repl.MdocSession$MdocApp.$init$$$anonfun$2(custom-inlined-numeric.md:145)
InlinedNumericMax
InlinedNumericMax is a helper trait for numeric custom refined types that have
only a maximum bound.
It gives you:
- upper-bound validation (
value <= maxValue) - built-in
predicateandinvalidReason - inline validation support for literals with
apply(compile-time), plus runtime validation withfrom
Use it when your type is a numeric value constrained to an upper-bound inclusive interval.
How to use
- Define your custom type as
type X = X.Type. XextendsInlinedNumericMax[A]fromrefined4s.types.numericorrefined4s.types.all.- Implement
maxValueasinline def. - Use
applyfor literals andfromfor runtime values.
Example
type ScoreOutOf100 = ScoreOutOf100.Type
object ScoreOutOf100 extends InlinedNumericMax[Int] {
override inline def maxValue: Int = 100
}
the maxValue has to be defined as inline def so that it can be used at compile-time.
Create with Compile-time Validation
Compile-time Validation (apply)
ScoreOutOf100(0)
// res12: Type = 0
ScoreOutOf100(100)
// res13: Type = 100
ScoreOutOf100(101)
// ScoreOutOf100(101)
// ^^^^^^^^^^^^^^^^^^
// Invalid value: [101]. It must be <= 100.
Create with Runtime Validation
Runtime Validation (from)
val scoreInput1 = 80
// scoreInput1: Int = 80
val scoreInput2 = 120
// scoreInput2: Int = 120
ScoreOutOf100.from(scoreInput1)
// res14: Either[String, Type] = Right(value = 80)
ScoreOutOf100.from(scoreInput2)
// res15: Either[String, Type] = Left(
// value = "Invalid value: [120]. It must be <= 100."
// )
Functional Runtime Handling
def describeScore(input: Int): String =
ScoreOutOf100.from(input).fold(
error => s"Invalid score input: $error",
score => s"Validated score: ${score.value}"
)
describeScore(95)
// res16: String = "Validated score: 95"
describeScore(120)
// res17: String = "Invalid score input: Invalid value: [120]. It must be <= 100."
Runtime Unsafe Validation (unsafeFrom)
unsafeFrom may throw an exception if the input value is invalid. So it is not recommended to use it.
ScoreOutOf100.unsafeFrom(scoreInput2)
// java.lang.IllegalArgumentException: Invalid value: [120]. It must be <= 100.
// at refined4s.RefinedBase.unsafeFrom$$anonfun$1(RefinedBase.scala:27)
// at scala.util.Either.fold(Either.scala:198)
// at refined4s.RefinedBase.unsafeFrom(RefinedBase.scala:27)
// at refined4s.RefinedBase.unsafeFrom$(RefinedBase.scala:7)
// at repl.MdocSession$MdocApp$ScoreOutOf100$.unsafeFrom(custom-inlined-numeric.md:156)
// at repl.MdocSession$MdocApp.$init$$$anonfun$3(custom-inlined-numeric.md:215)