【问题标题】:Type refinements in Scala but without using refinedScala 中的类型细化但不使用细化
【发布时间】:2020-05-19 08:41:51
【问题描述】:

我正在尝试创建一个基于 String 的 HexString 类型,它应该满足“它只包含十六进制数字”的条件,如果可能的话,我想让编译器为我检查它。

一个明显的解决方案是使用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")
  }
}

对于大多数代码库,运行时检查就足够了;但是,我可能需要在某个时候进行编译时检查,而且似乎没有办法在不使用精炼的情况下实现它。

如果我可以在不引入太多魔法的情况下尽可能地使代码本地化和易于理解,是否可以使用宏并指示编译器根据正则表达式测试分配的 RHS 并取决于它是否匹配或不是,它会创建一个 HexString 的实例或吐出编译器错误?

val ex1: HexString = "AF0" // HexString("AF0")
val ex2: HexString = "Hello World" // doesn't compile

除了我使用 Scala 元编写的 ADT 遍历和转换程序之外,我真的没有使用 Scala 宏的经验。

【问题讨论】:

    标签: scala macros scala-macros refinement-type refined


    【解决方案1】:

    如果您希望 fromStringLiteral 在编译时工作,您可以将其设为 macro(参见 sbt settings

    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
    

    【讨论】:

    • 看来这就是我想要的。我会遇到的一个小问题是,有问题的项目使用 Gradle 而不是 sbt,并且不确定您提供的设置链接是否会有所帮助......嗯,或者我应该创建一个简单的库并在那里使用 sbt ?
    • @StefanPavikevik 您应该设置您的构建工具(例如 gradle)。您可以使用 sbt 链接作为设置示例(您应该创建两个项目)。
    • @StefanPavikevik 您是否设法将 Gradle 设置为使用宏?
    • 我确实通过在具有自己的 build.gradle 文件的单独目录中设置单独的项目来以某种方式进行管理。然后我将它作为依赖项添加到主项目implementation project("macros") 非常感谢所有帮助!
    猜你喜欢
    • 2021-02-02
    • 1970-01-01
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    • 2019-03-17
    相关资源
    最近更新 更多