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