What does the @elidable annotation do in Scala, and when should I use it?

前端 未结 3 548
鱼传尺愫
鱼传尺愫 2021-02-05 05:09

I\'ve noticed in some of the scala library code, notably Predef, there is code like:

/** Tests an expression, throwing an `AssertionError` if false.         


        
相关标签:
3条回答
  • 2021-02-05 05:17

    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?

    0 讨论(0)
  • 2021-02-05 05:32

    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 ???.

    0 讨论(0)
  • 2021-02-05 05:33

    Short answer

    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).

    Long one

    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.

    0 讨论(0)
提交回复
热议问题