scala - Using Macro to Make Case Class -
given following macro (thanks @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 > 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 repl:
scala> import spire.math.jetdim import spire.math.jetdim scala> jetdim.validate(-55) <console>:9: error: -55 must > 0. jetdim.validate(-55) ^ scala> jetdim.validate(100) res1: int = 100 but, i'd build compile-time check (via jetdimmacro) 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 failed:
scala> import spire.math.jetdim import spire.math.jetdim scala> jetdim.build(-55) java.lang.illegalargumentexception: requirement failed @ scala.predef$.require(predef.scala:207) @ spire.math.jetdim.<init>(jet.scala:21) @ 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 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 @ scala.predef$.require(predef.scala:207) @ spire.math.jetdim.<init>(jet.scala:21) @ spire.math.jetdim$.apply(jet.scala:30) ... 43 elided i thought modify jetdimmacro#apply return jetdim rather int. however, jetdim lives in core project, which, see, depends on macros project (where jetdimmacro lives).
how can use validate method jetdim's companion object check positive int's @ compile-time?
the problem time call validate in apply no longer dealing constant (singleton type). so, validate gets non-constant int.
as alternative, try using implicit witness positive ints, jetdim takes constructor. instance, like:
package com.example case class jetdim(n: positiveint) case class positiveint(value: int) { require(value > 0) } then, add implicit (macro) conversion int => positiveint 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 use jetdim(12), pass, or jetdim(-12), fail (the macro expands int positiveint).
Comments
Post a Comment