代码之家  ›  专栏  ›  技术社区  ›  rampion

确定表达式的值在编译时是否已知

  •  3
  • rampion  · 技术社区  · 7 年前

    假设我想创建一个 NonZero 键入,使整数除法函数为total:

    def div(numerator: Int, denominator: NonZero): Int =
      numerator / denominator.value
    

    非零

    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 only 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 )?

    2 回复  |  直到 7 年前
        1
  •  0
  •   Yawar    7 年前

    Type Level Programming in Scala

    @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“继任者”类型,以及它如何转换为运行时整数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
  •  0
  •   rampion    7 年前

    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

    推荐文章