Что делает аннотация @elidable в Scala и когда я должен ее использовать? - PullRequest
24 голосов
/ 05 декабря 2011

Я заметил, что в некотором коде библиотеки 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 в этом классе вообще? Обратите внимание, что в этом примере я бы хотел, чтобы метод был удален из класса, потому что я не хотел бы, чтобы его вызывали.

Вторая часть: я видел , он предложил , чтобы это использовалось для устранения кода отладки журнала. Учитывая, что большинство фреймворков (особенно log4j) допускают настройку уровня ведения журнала во время выполнения, я не думаю, что это хороший вариант использования. Лично я хотел бы сохранить этот код. Итак, кроме методов assert () в Predef, каков хороший вариант использования для @elidable?

Ответы [ 3 ]

26 голосов
/ 05 декабря 2011

Краткий ответ

И метод, и все вызовы к нему просто исчезают. Это может быть хорошей идеей для ведения журналов, поскольку каждая инфраструктура журналирования вносит некоторые накладные расходы при вызове журналирования, но данный уровень отключен (вычисление эффективного уровня и подготовка аргументов).

Обратите внимание, что современные каркасы ведения журналов стараются максимально сократить этот объем (например, 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.

4 голосов
/ 06 декабря 2011

В качестве дополнения к 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) Подпись блока

ThisПодход подходит только для методов с (...) => Unit подписью.Если кто-то использует результат такого отключенного метода, как

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

, по крайней мере, мой компилятор Scala 2.9.1.final падает.Однако в такой подписи нет особого смысла.Потому что: какое значение должен возвращать такой отключенный метод?

2 голосов
/ 30 сентября 2012

На самом деле выражения не могут просто исчезнуть, потому что они имеют результат.Когда вы пропускаете вызов метода типа результата Boolean, вы получаете false и так далее.

Через несколько месяцев после того, как этот вопрос был опубликован, возникла проблема, чтобы решить, что делает элитное Ничто.Результатом стало повышение до ???.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...