给定以下宏(感谢@TravisBrown为此help):
JetDim.scala
case class JetDim(dimension: Int) {
require(dimension > 0)
}
object JetDim {
def validate(dimension: Int): Int = macro JetDimMacro.apply
def build(dimension: Int): JetDim = JetDim(validate(dimension))
}
JetDimMacro.scala
import reflect.macros.Context
object JetDimMacro {
sealed trait PosIntCheckResult
case class LteqZero(x: Int) extends PosIntCheckResult
case object NotConstant extends PosIntCheckResult
def apply(c: Context)(dimension: c.Expr[Int]): c.Expr[Int] = {
import c.universe._
getInt(c)(dimension) match {
case Right(_) => reify { dimension.splice }
case Left(LteqZero(x)) => c.abort(c.enclosingPosition, s"$x must be > 0.")
case Left(NotConstant) => reify { dimension.splice }
}
}
def getInt(c: Context)(dimension: c.Expr[Int]): Either[PosIntCheckResult, Int] = {
import c.universe._
dimension.tree match {
case Literal(Constant(x: Int)) => if (x > 0) Right(x) else Left(LteqZero(x))
case _ => Left(NotConstant)
}
}
}
它可以在REPL中使用:
scala> import spire.math.JetDim
import spire.math.JetDim
scala> JetDim.validate(-55)
<console>:9: error: -55 must be > 0.
JetDim.validate(-55)
^
scala> JetDim.validate(100)
res1: Int = 100
但是,我想将此编译时检查(通过
JetDimMacro
)构建到case类的apply
方法中。尝试1
case class JetDim(dimension: Int) {
require(dimension > 0)
}
object JetDim {
private def validate(dimension: Int): Int = macro JetDimMacro.apply
def build(dimension: Int): JetDim = JetDim(validate(dimension))
}
但这失败了:
scala> import spire.math.JetDim
import spire.math.JetDim
scala> JetDim.build(-55)
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:207)
at spire.math.JetDim.<init>(Jet.scala:21)
at spire.math.JetDim$.build(Jet.scala:26)
... 43 elided
尝试2
class JetDim(dim: Int) {
require(dim > 0)
def dimension: Int = dim
}
object JetDim {
private def validate(dimension: Int): Int = macro JetDimMacro.apply
def apply(dimension: Int): JetDim = {
validate(dimension)
new JetDim(dimension)
}
}
但是那也失败了:
scala> import spire.math.JetDim
import spire.math.JetDim
scala> JetDim(555)
res0: spire.math.JetDim = spire.math.JetDim@4b56f205
scala> JetDim(-555)
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:207)
at spire.math.JetDim.<init>(Jet.scala:21)
at spire.math.JetDim$.apply(Jet.scala:30)
... 43 elided
我想修改
JetDimMacro#apply
以返回JetDim
而不是Int
。但是,JetDim
驻留在core
项目中,据我所知,该项目取决于macros
项目(JetDimMacro
所在的位置)。如何在编译时使用
validate
的伴随对象中的JetDim
方法检查正整数? 最佳答案
问题在于,当我们在validate
中调用apply
时,我们不再处理常量(单子类型)。因此,validate得到一个非恒定的Int。
作为替代方案,您可以尝试将隐式见证用作正整数,然后JetDim将其用作构造函数。例如,类似:
package com.example
case class JetDim(n: PositiveInt)
case class PositiveInt(value: Int) {
require(value > 0)
}
然后,我们添加来自
Int => PositiveInt
的隐式(宏)转换来进行检查。import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object PositiveInt {
implicit def wrapConstantInt(n: Int): PositiveInt = macro verifyPositiveInt
def verifyPositiveInt(c: Context)(n: c.Expr[Int]): c.Expr[PositiveInt] = {
import c.universe._
val tree = n.tree match {
case Literal(Constant(x: Int)) if x > 0 =>
q"_root_.com.example.PositiveInt($n)"
case Literal(Constant(x: Int)) =>
c.abort(c.enclosingPosition, s"$x <= 0")
case x =>
c.abort(c.enclosingPosition, s"cannot verify $x > 0")
}
c.Expr(tree)
}
}
然后,您可以使用将通过的
JetDim(12)
或将失败的JetDim(-12)
(宏将Int扩展为PositiveInt)。关于scala - 使用宏制作案例类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29949563/