Усовершенствования типов в Scala, но без использования улучшенного
Я пытаюсь создать тип HexString на основе String, который должен удовлетворять условию "что он содержит только шестнадцатеричные цифры", и я хотел бы, чтобы компилятор проверил его тип, если это возможно.
Одним из очевидных решений было бы использовать refined и написать что-то вроде этого:
type HexString = String Refined MatchesRegex[W.`"""^(([0-9a-f]+)|([0-9A-F]+))$"""`.T]
refineMV[MatchesRegex[W.`"""^(([0-9a-f]+)|([0-9A-F]+))$"""`.T]]("AF0")
Теперь я не имею ничего против усовершенствованного, я считаю, что это немного излишне для того, что я пытаюсь сделать (и понятия не имею, собираюсь ли я вообще использовать его в других местах), и я не хочу импортировать библиотека, которая, я не уверен, будет использоваться более одного или двух раз в целом и содержит синтаксис, который может показаться волшебным (если не для меня, то для других разработчиков в команде).
С другой стороны, лучшее, что я могу написать с помощью чистого кода Scala, - это класс значений с интеллектуальными конструкторами, который мне подходит и кажется легким, за исключением того, что я не могу выполнять проверку типов во время компиляции. На данный момент это выглядит примерно так:
final case class HexString private (str: String) extends AnyVal {
// ...
}
object HexString {
def fromStringLiteral(literal: String): HexString = {
def isValid(str: String): Boolean = "\\p{XDigit}+".r.pattern.matcher(str).matches
if (isValid(literal)) HexString(literal)
else throw new IllegalArgumentException("Not a valid hexadecimal string")
}
}
Для большей части кодовой базы проверки во время выполнения достаточно как таковой; однако в какой-то момент мне может потребоваться проверка во время компиляции, и, похоже, нет никакого способа добиться этого, кроме использования refined.
Если я смогу сохранить код как можно более локализованным и понятным, не вводя особого волшебства, можно ли было бы использовать макрос и дать компилятору указание проверить правую частоту присваивания по отношению к регулярному выражению и, в зависимости от того, совпадает ли оно или нет, он создаст экземпляр HexString или выдаст ошибку компилятора?
val ex1: HexString = "AF0" // HexString("AF0")
val ex2: HexString = "Hello World" // doesn't compile
Помимо программ обхода и преобразования ADT, которые я написал с использованием мета Scala, у меня нет опыта работы с макросами Scala.
1 ответ
Если ты хочешь fromStringLiteral
для работы во время компиляции вы можете сделать это макросом (см. настройки sbt)
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def fromStringLiteral(literal: String): HexString = macro fromStringLiteralImpl
def fromStringLiteralImpl(c: blackbox.Context)(literal: c.Tree): c.Tree = {
import c.universe._
val literalStr = literal match {
case q"${s: String}" => s
case _ => c.abort(c.enclosingPosition, s"$literal is not a string literal")
}
if (isValid(literalStr)) q"HexString($literal)"
else c.abort(c.enclosingPosition, s"$literalStr is not a valid hexadecimal string")
}
потом
val ex1: HexString = HexString.fromStringLiteral("AF0") // HexString("AF0")
//val ex2: HexString = HexString.fromStringLiteral("Hello World") // doesn't compile
Если вы хотите, чтобы это работало как
import HexString._
val ex1: HexString = "AF0" // HexString("AF0")
//val ex2: HexString = "Hello World" // doesn't compile
тогда дополнительно вы можете сделать fromStringLiteral
неявное преобразование
implicit def fromStringLiteral(literal: String): HexString = macro fromStringLiteralImpl