【问题标题】:Determine whether an expression's value is known at compile time确定表达式的值在编译时是否已知
【发布时间】:2018-10-17 14:34:00
【问题描述】:

假设我想创建一个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 感觉很难看。

使用宏,我们可以定义applyonly for literals,所以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

我的宏中有could pattern match on Expr(Constant(_)),但是NonZero(X + 1) 呢?我宁愿不必实现自己的 scala 表达式求值器。

是否有帮助程序或一些简单的方法来确定在编译时是否知道赋予宏的表达式的值(C++ 将调用 constexpr)?

【问题讨论】:

标签: scala macros constant-expression


【解决方案1】:

如果忽略宏,那么在 Scala 中,编译时只存在类型,运行时只存在值。您可以在编译时使用类型级技巧将数字编码为类型,例如Type Level Programming in Scala

这是上述 Peano 算术示例的简化版本。首先,我们定义了一个类型类,它显示了某些类型如何转换为整数。

@annotation.implicitNotFound("Create an implicit of type TValue[${T}] to convert ${T} values to integers.")
final class TValue[T](val get: Int) extends AnyVal

然后,我们定义 Peano 'zero' 类型并展示它如何转换为运行时整数 0:

case object TZero {
  implicit val tValue: TValue[TZero.type] = new TValue(0)
}

然后是 Peano 'successor' 类型以及它如何转换为运行时整数 1 + 先前值:

case class TSucc[T: TValue]()
object TSucc {
  implicit def tValue[TPrev](implicit prevTValue: TValue[TPrev]): TValue[TSucc[TPrev]] =
    new TValue(1 + prevTValue.get)
}

然后测试安全划分:

object Test {
  def safeDiv[T](numerator: Int, denominator: TSucc[T])(implicit tValue: TValue[TSucc[T]]): Int =
    numerator / tValue.get
}

试一试:

scala> Test.safeDiv(10, TZero)
<console>:14: error: type mismatch;
 found   : TZero.type
 required: TSucc[?]
       Test.safeDiv(10, TZero)
                        ^

scala> Test.safeDiv(10, TSucc[String]())
<console>:14: error: Create an implicit of type TValue[String] to convert String values to integers.
       Test.safeDiv(10, TSucc[String]())
                                     ^

scala> Test.safeDiv(10, TSucc[TZero.type]) // 10/1
res2: Int = 10

scala> Test.safeDiv(10, TSucc[TSucc[TZero.type]]) // 10/2
res3: Int = 5

正如您可以想象的那样,这可能会很快变得冗长。

【讨论】:

    【解决方案2】:

    som-snytt's advice to check out ToolBox.eval 将我带到Context.eval,这是我一直想要的帮手:

    object NonZero {
      // ...
      def apply(n: Int): NonZero = macro apply_impl
      def apply_impl(c: Context)(n: c.Expr[Int]): c.Expr[NonZero] = try {
        if (c.eval(n) != 0) {
          import c.universe._
          c.Expr(q"NonZero.build(n).get")
        } else {
          throw new IllegalArgumentException("Non-zero value required")
        }
      } catch {
        case _: scala.tools.reflect.ToolBoxError =>
          throw new IllegalArgumentException("Unable to evaluate " + n.tree + " at compile time")
      }
    }
    

    所以现在我可以传递NonZero.apply 常量和由常量组成的表达式:

    scala> final val N = 3
    scala> NonZero(N)
    res0: NonZero = NonZero(3)
    scala> NonZero(2*N + 1)
    res1: NonZero = NonZero(7)
    scala> NonZero(N - 3)
    IllegalArgumentException: ...
    scala> NonZero((n:Int) => 2*n + 1)(3))
    IllegalArgumentException: ...
    

    虽然如果eval 可以像上面最后一个示例那样处理纯函数会很好,但这已经足够了。

    令人尴尬的是,从问题中回顾和重新测试我之前的代码证明我原来的宏也能处理相同的表达式!

    我断言 final val X = 3; NonZero(X) // compile-time error 是错误的,因为所有的评估都是通过内联处理的(正如 som-snytt 的评论所暗示的那样)。

    【讨论】:

      猜你喜欢
      • 2017-02-06
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 2019-03-14
      • 2017-05-01
      • 1970-01-01
      • 2012-05-14
      • 2015-05-16
      相关资源
      最近更新 更多