Предположим, я хочу создать тип NonZero
, чтобы моя целочисленная функция деления была общей:
def div(numerator: Int, denominator: NonZero): Int =
numerator / denominator.value
Я могу реализовать это, создав класс NonZero
с закрытым конструктором:
class NonZero private[NonZero] (val value : Int) { /*...*/ }
И вспомогательный объект для хранения конструктора Int => Option[NonZero]
и unapply
, так что его можно использовать в выражениях match
:
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
подходит для времени выполнениязначения, но необходимость делать NonZero.build(3).get
для литералов выглядит ужасно.
Используя макрос, мы можем определить apply
только для литералов , так что NonZero(3)
работает, но NonZero(0)
это ошибка времени компиляции:
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")
}
}
}
Однако этот макрос менее полезен, чем мог бы быть, так как он допускает только литералы, а не константные выражения времени компиляции:
final val X: Int = 3
NonZero(X) // compile-time error
I может соответствовать шаблону на Expr(Constant(_))
в моем макросе, но как насчет NonZero(X + 1)
?Я бы предпочел не реализовывать свой собственный оценщик выражений scala.
Есть ли помощник или какой-то простой способ определить, известно ли значение выражения, переданного макросу, во время компиляции (что C ++ будет называтьconstexpr
)