Аннотации макросов — это новый тип макросов, которые являются одним из кандидатов на включение (см. Также комментарий Юджина ниже) в предстоящем выпуске Scala 2.11. Тем не менее, благодаря недавно выпущенному плагину компилятора Macro Paradise Scala 2.10 с дополнительной опцией в настройках компилятора / SBT, вы можете использовать их сегодня, все еще используя стабильную версию Scala во время выполнения.
Один из вариантов использования аннотаций макросов, упомянутых в руководстве, — это AOP во время компиляции. Я решил попробовать реализовать нечто подобное, но для начала немного проще: автоматическая генерация методов делегатов (шаблон декоратора / шаблон прокси). На самом деле, несколько лет назад была предпринята аналогичная попытка использовать плагин компилятора ( плагин autoproxy ). В качестве дополнительной мотивации Лукаш недавно спросил в нашей технической комнате, имеет ли Scala именно такую функциональность — вместо того, чтобы сказать «Нет», я должен был сказать «Еще нет».
Результаты POC доступны на GitHub, в хранилище scala-macro-aop: https://github.com/adamw/scala-macro-aop . Если у вас есть SBT, вы можете поиграть с реализацией, просто вызвав команду run
из консоли SBT.
Как это работает? Допустим, у нас есть интерфейс Foo
с тремя методами (с очень оригинальными именами: method2
, method2
и method3
), каждый из которых принимает некоторые параметры. У нас есть реализация по умолчанию:
01
02
03
04
05
06
07
08
09
10
11
|
trait Foo { def method 1 (param 1 : String) : Int def method 2 (p 1 : Int, p 2 : Long) : Float def method 3 () : String } class FooImpl extends Foo { def method 1 (param 1 : String) = param 1 .length def method 2 (p 1 : Int, p 2 : Long) = p 1 + p 2 def method 3 () = "Hello World!" } |
Теперь мы хотели бы создать оболочку для экземпляра Foo
, которая делегировала бы все вызовы метода для данного экземпляра, если только метод не определен в оболочке.
Традиционное решение заключается в создании делегата для каждого метода вручную, например:
1
2
3
4
5
|
class FooWrapper(wrapped : Foo) extends Foo { def method 1 (param 1 : String) = wrapped.method 1 (param 1 ) def method 2 (p 1 : Int, p 2 : Long) = wrapped.method 2 (p 1 , p 2 ) def method 3 () = wrapped.method 3 () } |
Но это много работы. Используя макрос @delegate
, методы делегата теперь будут автоматически генерироваться во время компиляции ! То есть обёртка теперь становится:
1
2
3
4
|
class FooWrapper( @ delegate wrapped : Foo) extends Foo { // method1, method2 and method3 are generated at compile time // and delegate to the annotated parameter } |
Что если мы хотим реализовать некоторые методы? Макрос сгенерирует только недостающие:
1
2
3
4
|
class FooWrapper( @ delegate wrapped : Foo) extends Foo { def method 2 (p 1 : Int, p 2 : Long) = p 1 - p 1 // only method1 and method3 are generated } |
Поскольку реализация является просто POC, она будет работать только в простых случаях, то есть для методов с одним списком параметров, без параметров типа и когда метод не перегружен. Плюс код макроса, скажем, «еще не отшлифован».
Как упоминалось ранее, код находится на GitHub: https://github.com/adamw/scala-macro-aop , доступный по лицензии Apache2.