Усовершенствования типов в 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
Другие вопросы по тегам