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

Mat*_*ell 24 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")
}
Run Code Online (Sandbox Code Playgroud)

这个注释允许我在编译时消除代码.当我编译时-Xelide-below MAXIMUM,是吗?

  1. 删除方法和所有调用它?(如果是这样,如果另一个库希望这个方法在那里会发生什么?),我们得到NoSuchMethodError或者其他什么?
  2. 将方法保留在那里,但从方法中删除所有代码,留下一个空方法?
  3. 只是删除对方法的调用,但将方法保留在那里?

我可以用它来减少类的编译大小吗?所以,如果我有:

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

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

第二部分:我已经看到它建议用于消除调试日志代码.鉴于大多数框架(特别是log4j)允许运行时设置日志记录级别,我不认为这是一个很好的用例.就个人而言,我希望保留这些代码.所以除了assert()方法之外Predef,有什么好的用例@elidable呢?

Tom*_*icz 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")}
}
Run Code Online (Sandbox Code Playgroud)

证明-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
}
Run Code Online (Sandbox Code Playgroud)

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

$ scalac -Xelide-below 900 Foobar.scala && javap -c Foobar
Run Code Online (Sandbox Code Playgroud)

调用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

}
Run Code Online (Sandbox Code Playgroud)

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


bin*_*ADa 5

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

(1) C++风格

因为我来自 C++ 我已经定义了

/** ''Switch'' between '''Debug''' and '''Release''' version. */
object BuildLevel {
  type only = annotation.elidable
  final val DEBUG = annotation.elidable.INFO
}
Run Code Online (Sandbox Code Playgroud)

并在旧的 C++ 预处理器风格中使用它,例如

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

override def compare(that: ): Int = {
  checkExpensive(...)
  ...
}
Run Code Online (Sandbox Code Playgroud)

标记我想在发布版本中关闭的昂贵检查(检查必须始终为真的前提条件或不变量)。

当然,这只是类似于assert 用例,除了在应该整体关闭的单独方法中重构出昂贵的代码的区别。但这一切只对非常昂贵的支票才值得。在一个 10k 行的项目中,我只有 3 个标记的检查。更便宜的测试我不会关闭并留在代码中,因为它们增加了它的健壮性

(2) 单位签名

此方法仅适用于具有(...) => Unit签名的方法。如果使用这种关闭方法的结果,例如

@only(DEBUG)
def checkExpensive(that: Any): Int = {
  4
}
val n = checkExpensive(this)
Run Code Online (Sandbox Code Playgroud)

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