Аннотации макросов — это новый тип макросов, которые являются одним из кандидатов на включение (см. Также комментарий Юджина ниже) в предстоящем выпуске 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 method1(param1: String): Int def method2(p1: Int, p2: Long): Float def method3(): String}class FooImpl extends Foo { def method1(param1: String) = param1.length def method2(p1: Int, p2: Long) = p1 + p2 def method3() = "Hello World!"} |
Теперь мы хотели бы создать оболочку для экземпляра Foo , которая делегировала бы все вызовы метода для данного экземпляра, если только метод не определен в оболочке.
Традиционное решение заключается в создании делегата для каждого метода вручную, например:
|
1
2
3
4
5
|
class FooWrapper(wrapped: Foo) extends Foo { def method1(param1: String) = wrapped.method1(param1) def method2(p1: Int, p2: Long) = wrapped.method2(p1, p2) def method3() = wrapped.method3()} |
Но это много работы. Используя макрос @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 method2(p1: Int, p2: Long) = p1 - p1 // only method1 and method3 are generated} |
Поскольку реализация является просто POC, она будет работать только в простых случаях, то есть для методов с одним списком параметров, без параметров типа и когда метод не перегружен. Плюс код макроса, скажем, «еще не отшлифован».
Как упоминалось ранее, код находится на GitHub: https://github.com/adamw/scala-macro-aop , доступный по лицензии Apache2.