I\'ve noticed in some of the scala library code, notably Predef, there is code like:
/** Tests an expression, throwing an `AssertionError` if false.
As a complement to Tomasz Nurkiewicz's answer two comments.
(1) C++ style
Because I came from C++ I've defined
/** ''Switch'' between '''Debug''' and '''Release''' version. */
object BuildLevel {
type only = annotation.elidable
final val DEBUG = annotation.elidable.INFO
}
and use this in good old C++ preprocessor style like
import BuildLevel._
@only(DEBUG)
private def checkExpensive(...) {
...
}
override def compare(that: ): Int = {
checkExpensive(...)
...
}
to mark expensive checks (check of pre-conditions or invariants that must always holds true) that I want to switch off in release builds.
Of course that's just similar to the assert use case except for the difference of refactoring out expensive code in a separate method that should be switched off as a whole. But all this is only worthwhile for really expensive checks. In a 10k lines project I have only 3 marked checks. Cheaper tests I wouldn't switch off and leave in the code, because they increase its robustness.
(2) Unit signature
This approach is suitable only for methods with a (...) => Unit signature. If one use a result of such a switched off method like
@only(DEBUG)
def checkExpensive(that: Any): Int = {
4
}
val n = checkExpensive(this)
at least my Scala 2.9.1.final compiler crashes. However, there is not much sense in such a signature. Because: Which value should such a switched off method return?
Actually, expressions can't just disappear, because they have a result. When you elide an invocation of a method of result type Boolean, you wind up with false, and so on.
There was an issue a few months after this question was posted to settle what eliding Nothing does. The outcome was to elide to ???.
Both method and all calls to it simply disappear. This might be a good idea to use for logging since every logging framework introduces some overhead when logging is called but a given level is disabled (computing the effective level and preparing arguments).
Note that modern logging frameworks try to reduce this footprint as much as possible (e.g. Logback optimizes is*Enabled() calls and SLF4S passes message by name to avoid unnecessary string concatenations).
My test code:
import scala.annotation.elidable
import scala.annotation.elidable._
class Foobar {
info()
warning()
@elidable(INFO) def info() {println("INFO")}
@elidable(WARNING) def warning() {println("WARNING")}
}
Proves that with -Xelide-below 800 both statements are printed while with 900 only "WARNING" appears. So what happens under the hood?
$ 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
}
As you can see this compiles normally. However when this instruction is used:
$ scalac -Xelide-below 900 Foobar.scala && javap -c Foobar
calls to info() and the method itself disappears from the bytecode:
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
}
I would expect that NoSuchMethodError is thrown at runtime when removed method is called from client code compiled against Foobar version with lower elide-below threshold . Also it smells like good old C preprocessor, and as such I would think twice before employing @elidable.