Opaque Types y Refined

Usando conjuntamente tipos y pocos y refined para validar datos en Scala 3.

Introducción

En Scala 2 eran bastante común usar las librerías refined y newtype para validar datos y obtener representaciones ligeras (sin todas la contrapartidas de los value objects en cuanto a creación de objetos y sus consecuencias) de los tipos de datos usados en nuestro dominio. El libro de Grabriel Volpe es una fantástica referencia para ver este patrón en acción.

En Scala 3 aquélla segunda librería no puede usarse, pero podemos en cambio emplear tipos opacos junto con refined para algo similar, aunque su uso es extremadamente más verboso. Tanto en esta como en esta otra conversación hay información y opciones interesantes al respecto. Voy a explicar cómo hago para emplear ambas de forma conjunta.

Aproximación

type AgeR = Int Refined Greater[18]
object AgeR extends RefinedTypeOps[AgeR, Int]
opaque type Age <: Int = Int
object Age {
  def apply(a: Int): Age = a
  def safe(a: Int): Either[String, Age] =
    AgeR.from(a).map(_.value)
  def validate(a: Int): Validated[String, Age] =
    AgeR.from(a).fold(
      e => Validated.Invalid(e),
      a => Validated.Valid(Age(a.value))
    )
}