使用宏来创建案例类(Using Macro to Make Case Class)

编程入门 行业动态 更新时间:2024-10-28 08:31:30
使用宏来创建案例类(Using Macro to Make Case Class)

给出以下宏(感谢@TravisBrown提供此帮助 ):

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 ) 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所在的JetDimMacro )。

如何在JetDim的伴随对象中使用此validate方法在编译时检查正int?

Given the following macro (thanks @TravisBrown for this 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) } } }

It works from the 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

But, I'd like to build this compile-time check (via the JetDimMacro) into the case class's apply method.

Attempt 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)) }

But that failed:

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

Attempt 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) } }

Yet that failed too:

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

I thought to modify JetDimMacro#apply to return a JetDim rather than an Int. However, JetDim lives in the core project, which, from what I see, depends on the macros project (where JetDimMacro lives).

How can I use this validate method from JetDim's companion object to check for positive int's at compile-time?

最满意答案

问题是,当我们在apply调用validate ,我们不再处理常量 (单例类型)。 因此,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)。

The problem is that by the time we call validate in apply we are no longer dealing with a constant (singleton type). So, validate gets a non-constant Int.

As an alternative, you could try using an implicit witness for positive ints, which JetDim then takes as a constructor. For instance, something like:

package com.example case class JetDim(n: PositiveInt) case class PositiveInt(value: Int) { require(value > 0) }

Then, we add an implicit (macro) conversion from Int => PositiveInt that does your check.

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) } }

You can then use JetDim(12), which will pass, or JetDim(-12), which will fail (the macro expands the Int to a PositiveInt).

更多推荐

本文发布于:2023-08-07 14:23:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1464750.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:案例   Macro   Class   Case

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!