我注意到一些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
进行编译时,是吗
我可以用它来减少类的编译大小吗?如果我有:
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
的一个很好的用例是什么?
答案 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
,依此类推。
在发布这个问题几个月之后发生了一个问题,解决了什么都没有做到。结果是要忽视???
。