【问题标题】:What does the @elidable annotation do in Scala, and when should I use it?@elidable 注解在 Scala 中有什么作用,我应该什么时候使用它?
【发布时间】:2011-12-05 17:02:45
【问题描述】:

我注意到在一些 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. 只删除对方法的调用,但将方法保留在那里?

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

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 有什么好的用例?

【问题讨论】:

    标签: scala


    【解决方案1】:

    作为Tomasz Nurkiewicz的回答两个cmets的补充。

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

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

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

    (2) 单位签名

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

    @only(DEBUG)
    def checkExpensive(that: Any): Int = {
      4
    }
    val n = checkExpensive(this)
    

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

    【讨论】:

      【解决方案2】:

      简答

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

      请注意,现代日志框架试图尽可能减少这种占用空间(例如,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 阈值编译的客户端代码调用删除的方法时抛出。而且它闻起来像老式的 C 预处理器,因此在使用 @elidable 之前我会三思而后行。

      【讨论】:

        【解决方案3】:

        实际上,表达式不能只是消失,因为它们有结果。当您省略对结果类型为布尔值的方法的调用时,您将得到false,依此类推。

        在发布此问题几个月后出现了一个问题,以解决省略 Nothing 的作用。结果是逃到???

        【讨论】:

          猜你喜欢
          • 2014-09-29
          • 2011-02-21
          • 2012-06-02
          • 2023-04-10
          • 1970-01-01
          • 2015-05-19
          • 2012-06-18
          • 1970-01-01
          • 2011-01-18
          相关资源
          最近更新 更多