Статьи

Автоматическая генерация методов делегатов с помощью макроаннотаций

Аннотации макросов — это новый тип макросов, которые являются одним из кандидатов на включение (см. Также комментарий Юджина ниже) в предстоящем выпуске 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.