Scala - 如何"延迟"表达式的编译

Bar*_*zak 5 scala scala-macros

我一直想为Scala实现一个链式比较运算符,但经过几次尝试后,我认为没有办法实现它.这是它应该如何工作:

val a = 3
1 < a < 5 //yields true
3 < a < 5 //yields false
Run Code Online (Sandbox Code Playgroud)

问题是,scala编译器在计算表达式时非常贪婪,因此上面的表达式计算如下:

1 < a    //yields true
true < 5 //compilation error
Run Code Online (Sandbox Code Playgroud)

我试图编写代码以某种方式实现它,这是我尝试过的:

  • 从类型Int到我的类型的隐式转换RichComparisonInt- 由于上面的评估方式没有帮助,
  • 覆盖类Int我的同班同学-不能完成,因为Intabstractfinal,
  • 我尝试case class用名字创建<,就像::,但后来我发现,这个类只是为了模式匹配而创建的,
  • 我想创建隐式转换=> Boolean,它可以在编译级别上工作,但是没有办法提取操作的参数,从而导致Boolean结果.

有没有办法在Scala中做到这一点?也许宏可以完成这项工作?

Ben*_*ich 3

这是一个使用macros. 这里的一般方法是丰富Boolean,以便它有一个macro方法可以查看prefix上下文的 来查找用于生成该 的比较Boolean

例如,假设我们有:

implicit class RichBooleanComparison(val x: Boolean) extends AnyVal {
  def <(rightConstant: Int): Boolean = macro Compare.ltImpl
}
Run Code Online (Sandbox Code Playgroud)

以及macro带有方法头的定义:

def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean]
Run Code Online (Sandbox Code Playgroud)

现在假设编译器正在解析表达式1 < 2 < 3。我们显然可以在评估宏方法体时使用c.prefix来获取表达式。然而,恒定折叠1 < 2的概念阻止我们在这里这样做。常量折叠是编译器在编译时计算预定常量的过程。因此,当宏被求值时,已经被折叠到这种情况下。我们已经失去了导致 的表达方式。您可以在这个问题上阅读有关常量折叠及其与 Scala 宏的交互的更多信息,以及有关此问题的一些内容。c.prefixtrue1 < 2true

如果我们可以将讨论的范围限制为仅形式的表达式C1 < x < C2,其中C1C2是常量,并且x是变量,那么这是可行的,因为这种类型的表达式不会受到常量折叠的影响。这是一个实现:

object Compare {
  def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] = {
    import c.universe._
    c.prefix.tree match {
      case Apply(_, Apply(Select(lhs@Literal(Constant(_)), _), (x@Select(_, TermName(_))) :: Nil) :: Nil) => 
          val leftConstant = c.Expr[Int](lhs)
          val variable = c.Expr[Int](x)
          reify((leftConstant.splice < variable.splice) && (variable.splice < rightConstant.splice))

      case _ => c.abort(c.enclosingPosition, s"Invalid format.  Must have format c1<x<c2, where c1 and c2 are constants, and x is variable.")
    }
  } 
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们将上下文prefix与预期类型相匹配,提取相关部分 (lhsx),使用 构造新的子树,并使用和c.Expr[Int]构造新的完整表达式树以进行所需的三向比较。如果与预期类型不匹配,则编译失败。 reifysplice

这使我们能够:

val x = 5
1 < x < 5 //true
6 < x < 7 //false
3 < x < 4 //false
Run Code Online (Sandbox Code Playgroud)

如预期的!

有关宏树的文档和本演示文稿是了解有关宏的更多信息的良好资源。