@elidable注释在Scala中做了什么,我什么时候应该使用它?

时间:2011-12-05 17:02:46

标签: scala

我注意到一些scala库代码,特别是Predef,代码如下:

/** Tests an expression, throwing an `AssertionError` if false.
*  Calls to this method will not be generated if `-Xelide-below`
*  is at least `ASSERTION`.
*
*  @see elidable
*  @param p   the expression to test
*/
@elidable(ASSERTION)
def assert(assertion: Boolean) {
if (!assertion)
  throw new java.lang.AssertionError("assertion failed")
}

这个注释允许我在编译时消除代码。当我使用-Xelide-below MAXIMUM进行编译时,是吗

  1. 删除方法和所有调用它? (如果是这样,如果另一个库希望这个方法在那里会发生什么?),我们是否得到NoSuchMethodError或其他什么?
  2. 将方法保留在那里,但是从方法中删除所有代码,留下一个空方法?
  3. 只是删除对方法的调用,但是将方法保留在那里?
  4. 我可以用它来减少类的编译大小吗?如果我有:

    class Foobar {
        // extremely expensive toString method for debugging purposes
        @elidable(FINE) def toString(): String = "xxx"
    }
    

    并使用-Xelide-below WARNING编译,这个类中的toString会完全消失吗?请注意,在这个例子中,我希望从类中删除该方法,因为我不希望它被调用的可能性。

    第二部分:我见过it suggested这用于消除调试日志代码。鉴于大多数框架(特别是log4j)允许运行时设置日志记录级别,我不认为这是一个很好的用例。就个人而言,我希望保留这些代码。除了Predef中的assert()方法之外,@elidable的一个很好的用例是什么?

3 个答案:

答案 0 :(得分:26)

简短回答

方法和所有对它的调用都消失了。这可能是一个用于日志记录的好主意,因为每个日志记录框架都会在调用日志记录时引入一些开销,但禁用给定级别(计算有效级别并准备参数)。

请注意,现代日志记录框架会尽可能地减少此占用空间(例如Logback优化is*Enabled()调用,SLF4S按名称传递消息,以避免不必要的字符串连接。)

长一个

我的测试代码:

import scala.annotation.elidable
import scala.annotation.elidable._

class Foobar {
    info()
    warning()

    @elidable(INFO) def info() {println("INFO")}
    @elidable(WARNING) def warning() {println("WARNING")}
}

证明,-Xelide-below 800只有900时才会打印两个语句,只显示"WARNING"。那么幕后会发生什么?

$ scalac -Xelide-below 800 Foobar.scala && javap -c Foobar

public class Foobar extends java.lang.Object implements scala.ScalaObject{
public void info();
//...

public void warning();
//...

public Foobar();
  Code:
   0:   aload_0
   1:   invokespecial   #26; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   invokevirtual   #30; //Method info:()V
   8:   aload_0
   9:   invokevirtual   #32; //Method warning:()V
   12:  return
}

正如您所看到的,这可以正常编译。但是,当使用此指令时:

$ scalac -Xelide-below 900 Foobar.scala && javap -c Foobar

调用info(),方法本身从字节码中消失

public class Foobar extends java.lang.Object implements scala.ScalaObject{
public void warning();
//...

public Foobar();
  Code:
   0:   aload_0
   1:   invokespecial   #23; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   invokevirtual   #27; //Method warning:()V
   8:   return

}

我希望在运行时抛出NoSuchMethodError,当从针对具有较低Foobar阈值的elide-below版本编译的客户端代码调用remove方法时。它也闻起来像老C预处理器,因此在使用@elidable之前我会三思而行。

答案 1 :(得分:4)

作为Tomasz Nurkiewicz回答两条评论的补充。

(1)C ++风格

因为我来自C ++,所以我定义了

/** ''Switch'' between '''Debug''' and '''Release''' version. */
object BuildLevel {
  type only = annotation.elidable
  final val DEBUG = annotation.elidable.INFO
}

并以良好的旧C ++预处理器样式(如

)使用它
import BuildLevel._
@only(DEBUG)
private def checkExpensive(...) {
  ...
}

override def compare(that: ): Int = {
  checkExpensive(...)
  ...
}

标记昂贵的检查(检查必须始终成立的前置条件或不变量)我想在发布中关闭 生成。

当然,这与断言用例类似,不同之处在于,在一个单独的方法中重构昂贵的代码的差异应该作为一个整体关闭。但所有这些只对真正昂贵的支票才有价值。在10k行项目中,我只有3个标记检查。更便宜的测试我不会关闭并留下代码,因为它们增加了健壮性

(2)单位签名

此方法仅适用于具有(...) => Unit签名的方法。如果使用像

这样的关闭方法的结果
@only(DEBUG)
def checkExpensive(that: Any): Int = {
  4
}
val n = checkExpensive(this)

至少我的Scala 2.9.1.final编译器崩溃了。但是,这种签名没有多大意义。因为:这样的关闭方法应返回哪个值?

答案 2 :(得分:2)

实际上,表达式不能只是消失,因为它们有结果。当您忽略调用结果类型为Boolean的方法时,最终会使用false,依此类推。

在发布这个问题几个月之后发生了一个问题,解决了什么都没有做到。结果是要忽视???