Scala - 如何 "delay" 表达式的编译
Scala - How to "delay" expression's compilation
我一直想为 Scala 实现链式比较运算符,但经过几次尝试,我认为没有办法做到这一点。它应该是这样工作的:
val a = 3
1 < a < 5 //yields true
3 < a < 5 //yields false
问题是,scala 编译器在计算表达式时非常贪婪,所以上面的表达式计算如下:
1 < a //yields true
true < 5 //compilation error
我尝试编写代码来以某种方式实现它,这是我尝试过的:
- 从类型
Int
到我的类型 RichComparisonInt
的隐式转换 - 由于上图所示的评估方式没有帮助,
- 用我的 class 覆盖 class
Int
- 无法完成,因为 Int
既是 abstract
又是 final
,
- 我试过使用名称
<
创建 case class
,就像 ::
一样,但后来我发现,这个 class 只是为了模式匹配,
- 我已经考虑过从
=> Boolean
创建隐式转换,这将在编译级别上工作,但是没有办法提取操作的参数,这导致了 Boolean
结果。
在 Scala 中有什么方法可以做到吗?也许宏可以完成这项工作?
如果不使用宏,您将无法命名方法 <
,因为编译器将始终选择实际在 Int
上的 <
方法,而不是任何方法丰富 class。但是,如果您可以放弃它,则可以按照您的建议丰富 Int
,并且 return 是一种中间类型,可以跟踪到目前为止的比较:
implicit class RichIntComparison(val x: Int) extends AnyVal {
def <<<(y: Int) = Comparison(x < y, y)
}
case class Comparison(soFar: Boolean, y: Int) {
def <<<(z: Int) = soFar && (y < z)
}
那么我们可以这样做:
1 <<< 2 <<< 3
//Equivalent to:
val rc: Comparison = RichIntComparison(1).<<<(2)
rc.<<<(3)
如果需要,您还可以添加从 Comparison
到 Boolean
的隐式转换,以便您也可以使用 <<<
进行半比较:
object Comparison {
implicit def comparisonToBoolean(c: Comparison): Boolean = c.soFar
}
您可以这样做:
val comp1: Boolean = 1 <<< 2 //true
val comp2: Boolean = 1 <<< 2 <<< 3 //true
现在,一旦您引入了这种隐式转换,您就可以返回并在 Comparison
return Comparison
上创建 <<<
,从而允许您做更多事情扩展链接:
case class Comparison(soFar: Boolean, y: Int) {
def <<<(z: Int): Comparison = Comparison(soFar && (y < z), z)
//You can also use < for everything after the first comparison:
def <(z: Int) = <<<(z)
}
//Now, we can chain:
val x: Boolean = 1 <<< 2 <<< 3 <<< 4 <<< 3 //false
val x: Boolean = 1 <<< 2 < 3 < 4 < 7 //true
这是一个使用 macros
的解决方案。这里的一般方法是丰富 Boolean
以便它有一个 macro
方法查看上下文的 prefix
以找到用于生成 Boolean
的比较.
例如,假设我们有:
implicit class RichBooleanComparison(val x: Boolean) extends AnyVal {
def <(rightConstant: Int): Boolean = macro Compare.ltImpl
}
还有一个 macro
带有方法头的定义:
def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean]
现在假设编译器正在解析表达式 1 < 2 < 3
。在评估宏方法体时,我们显然可以使用 c.prefix
来获取表达式 1 < 2
。但是,constant folding prevents us from doing so here. Constant folding is the process by which the compiler computes predetermined constants at compile time. So by the time macros are being evaluated, the c.prefix
has already been folded to be just true
in this case. We have lost the 1 < 2
expression that led to true
. You can read more about constant folding and their interactions with Scala macros on this issue and a little bit on this question的概念。
如果我们可以将讨论范围限制在 C1 < x < C2
形式的表达式,其中 C1
和 C2
是常量,而 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.")
}
}
}
在这里,我们将上下文 prefix
匹配到预期类型,提取相关部分(lhs
和 x
),使用 c.Expr[Int]
构造新的子树,以及使用 reify
和 splice
构建一个新的完整表达式树,以进行所需的三向比较。如果与预期类型不匹配,将无法编译。
这让我们可以做到:
val x = 5
1 < x < 5 //true
6 < x < 7 //false
3 < x < 4 //false
随心所欲!
docs about macros, trees, and this presentation 是学习更多宏的好资源。
我一直想为 Scala 实现链式比较运算符,但经过几次尝试,我认为没有办法做到这一点。它应该是这样工作的:
val a = 3
1 < a < 5 //yields true
3 < a < 5 //yields false
问题是,scala 编译器在计算表达式时非常贪婪,所以上面的表达式计算如下:
1 < a //yields true
true < 5 //compilation error
我尝试编写代码来以某种方式实现它,这是我尝试过的:
- 从类型
Int
到我的类型RichComparisonInt
的隐式转换 - 由于上图所示的评估方式没有帮助, - 用我的 class 覆盖 class
Int
- 无法完成,因为Int
既是abstract
又是final
, - 我试过使用名称
<
创建case class
,就像::
一样,但后来我发现,这个 class 只是为了模式匹配, - 我已经考虑过从
=> Boolean
创建隐式转换,这将在编译级别上工作,但是没有办法提取操作的参数,这导致了Boolean
结果。
在 Scala 中有什么方法可以做到吗?也许宏可以完成这项工作?
如果不使用宏,您将无法命名方法 <
,因为编译器将始终选择实际在 Int
上的 <
方法,而不是任何方法丰富 class。但是,如果您可以放弃它,则可以按照您的建议丰富 Int
,并且 return 是一种中间类型,可以跟踪到目前为止的比较:
implicit class RichIntComparison(val x: Int) extends AnyVal {
def <<<(y: Int) = Comparison(x < y, y)
}
case class Comparison(soFar: Boolean, y: Int) {
def <<<(z: Int) = soFar && (y < z)
}
那么我们可以这样做:
1 <<< 2 <<< 3
//Equivalent to:
val rc: Comparison = RichIntComparison(1).<<<(2)
rc.<<<(3)
如果需要,您还可以添加从 Comparison
到 Boolean
的隐式转换,以便您也可以使用 <<<
进行半比较:
object Comparison {
implicit def comparisonToBoolean(c: Comparison): Boolean = c.soFar
}
您可以这样做:
val comp1: Boolean = 1 <<< 2 //true
val comp2: Boolean = 1 <<< 2 <<< 3 //true
现在,一旦您引入了这种隐式转换,您就可以返回并在 Comparison
return Comparison
上创建 <<<
,从而允许您做更多事情扩展链接:
case class Comparison(soFar: Boolean, y: Int) {
def <<<(z: Int): Comparison = Comparison(soFar && (y < z), z)
//You can also use < for everything after the first comparison:
def <(z: Int) = <<<(z)
}
//Now, we can chain:
val x: Boolean = 1 <<< 2 <<< 3 <<< 4 <<< 3 //false
val x: Boolean = 1 <<< 2 < 3 < 4 < 7 //true
这是一个使用 macros
的解决方案。这里的一般方法是丰富 Boolean
以便它有一个 macro
方法查看上下文的 prefix
以找到用于生成 Boolean
的比较.
例如,假设我们有:
implicit class RichBooleanComparison(val x: Boolean) extends AnyVal {
def <(rightConstant: Int): Boolean = macro Compare.ltImpl
}
还有一个 macro
带有方法头的定义:
def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean]
现在假设编译器正在解析表达式 1 < 2 < 3
。在评估宏方法体时,我们显然可以使用 c.prefix
来获取表达式 1 < 2
。但是,constant folding prevents us from doing so here. Constant folding is the process by which the compiler computes predetermined constants at compile time. So by the time macros are being evaluated, the c.prefix
has already been folded to be just true
in this case. We have lost the 1 < 2
expression that led to true
. You can read more about constant folding and their interactions with Scala macros on this issue and a little bit on this question的概念。
如果我们可以将讨论范围限制在 C1 < x < C2
形式的表达式,其中 C1
和 C2
是常量,而 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.")
}
}
}
在这里,我们将上下文 prefix
匹配到预期类型,提取相关部分(lhs
和 x
),使用 c.Expr[Int]
构造新的子树,以及使用 reify
和 splice
构建一个新的完整表达式树,以进行所需的三向比较。如果与预期类型不匹配,将无法编译。
这让我们可以做到:
val x = 5
1 < x < 5 //true
6 < x < 7 //false
3 < x < 4 //false
随心所欲!
docs about macros, trees, and this presentation 是学习更多宏的好资源。