Suppose I want to create a NonZero
type so that my integer division function is total:
def div(numerator: Int, denominator: NonZero): Int =
numerator / denominator.value
I can implement this by creating a NonZero
class with a private constructor:
class NonZero private[NonZero] (val value : Int) { /*...*/ }
And a helper object to hold a Int => Option[NonZero]
constructor, and an unapply
so it can be used in match
expressions:
object NonZero {
def build(n:Int): Option[NonZero] = n match {
case 0 => None
case n => Some(new NonZero(n))
}
def unapply(nz: NonZero): Option[Int] = Some(nz.value)
// ...
}
build
is fine for runtime values, but having to do NonZero.build(3).get
for literals feels ugly.
Using a macro, we can define apply
only for literals, so NonZero(3)
works, but NonZero(0)
is a compile-time error:
object NonZero {
// ...
def apply(n: Int): NonZero = macro apply_impl
def apply_impl(c: Context)(n: c.Expr[Int]): c.Expr[NonZero] = {
import c.universe._
n match {
case Expr(Literal(Constant(nValue: Int))) if nValue != 0 =>
c.Expr(q"NonZero.build(n).get")
case _ => throw new IllegalArgumentException("Expected non-zero integer literal")
}
}
}
However this macro is less useful than it could be, as it only allows literals, not compile-time constant expressions:
final val X: Int = 3
NonZero(X) // compile-time error
I could pattern match on Expr(Constant(_))
in my macro, but then what about NonZero(X + 1)
? I'd rather not have to implement my own scala expression evaluator.
Is there a helper or some easy way to determine if the value of an expression given to a macro is known at compile time (what C++ would call constexpr
)?