使用宏来制作案例 Class
Using Macro to Make Case Class
给定以下宏(感谢 @TravisBrown for this ):
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
)构建到案例 class 的 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
修改为 return 一个 JetDim
而不是 Int
。但是,JetDim
住在 core
项目中,据我所知,该项目取决于 macros
项目(JetDimMacro
所在的项目)。
如何使用 JetDim
的伴随对象中的 validate
方法在编译时检查正整数?
问题在于,当我们在 apply
中调用 validate
时,我们不再处理 constant(单例类型)。因此,validate 得到一个非常量 Int.
作为替代方案,您可以尝试对正整数使用隐式 witness,然后 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)。
给定以下宏(感谢 @TravisBrown for this
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
)构建到案例 class 的 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
修改为 return 一个 JetDim
而不是 Int
。但是,JetDim
住在 core
项目中,据我所知,该项目取决于 macros
项目(JetDimMacro
所在的项目)。
如何使用 JetDim
的伴随对象中的 validate
方法在编译时检查正整数?
问题在于,当我们在 apply
中调用 validate
时,我们不再处理 constant(单例类型)。因此,validate 得到一个非常量 Int.
作为替代方案,您可以尝试对正整数使用隐式 witness,然后 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)。