2016-03-27 3 views
2

Предположим, у меня есть черта Foo с несколькими методами. Я хочу создать новый признак, который расширяет Foo, но «обертывает» каждый вызов метода, например, с помощью некоторого заявления печати (на самом деле это будет чем-то более сложным/у меня есть несколько различных вариантов использования).Миксин для обертывания каждого метода черты Scala

Я могу сделать это вручную, переопределив каждый метод. Но это, кажется, излишне многословным (и все слишком легко вызвать неправильный супер метод):

object FooWrapped extends FooWrapped 
trait FooWrapped extends Foo { 
    override def bar(x: Int) ={ 
    println("call") 
    super.bar(x) 
    } 
    override def baz(y: Int) ={ 
    println("call") 
    super.baz(y) 
    } 
} 

scala> FooWrapped.bar(3) 
call 
res3: Int = 6 

Я надеялся написать подмешать черту, что я мог бы повторно использовать с другими признаками, а также использовать как:

trait FooWrapped extends Foo with PrintCall 

Таким образом, мне не нужно вручную переопределять каждый метод (mixin сделает это для меня).

Можно ли написать такую ​​особенность mixin в Scala? Как бы это выглядело?

+0

Это должно быть выполнимо с помощью макросов аннотаций (HTTP: //docs.scala-lang .org/overviews/macro/annotations), я думаю. Это комментарий, а не ответ, потому что я не могу написать макрос на данный момент. –

+0

Вы также можете использовать шаблон стекируемых признаков, чтобы добавить определенное поведение к существующим признакам, так что ваши вызовы выглядят так: val a = new SomeClass with PrintCall с Logger with Whatever. Вы просто вводите поведение в том порядке, в котором хотите, чтобы они выполнялись (в случае печати и регистрации это не имеет значения, но если ваши черты - это AddTwo и MultiplyByThree, то порядок имеет значение). Я пишу в комментарии, потому что это не то, о чем вы просили, но если вы не знакомы с этим шаблоном, проверьте это, это может быть довольно круто (однако вам нужно явно переопределить каждый метод). – slouc

+0

@slouc Я надеюсь на решение, которое не означает, что я должен написать 'override def bar' вручную для каждого метода. В основном, поскольку это означает, что я не могу написать это в общем виде (не зная заранее всех методов и подписей). –

ответ

2

Обновление Это макрос. Это было гораздо менее болезненно, чем я думал, что это будет из-за квазиквартир. Они замечательные. Этот код делает только немного, и вам, вероятно, придется его улучшить. Он может не учитывать некоторые особые ситуации. Также предполагается, что ни у родительского класса, ни у его метода нет параметров типа, он обертывает только методы данного класса или признака, но не методы его родителей, он может не работать, если у вас есть вспомогательные конструкторы и т. Д. Тем не менее, я надеюсь, что это даст вам идея о том, как сделать это для ваших конкретных потребностей, заставляя ее работать во всех ситуациях, к сожалению, слишком большая работа для меня прямо сейчас.

object MacrosLogging { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.blackbox 


    def log_wrap[T](): T = macro log_impl[T] 
    def log_impl[T : c.WeakTypeTag](c: blackbox.Context)(): c.Expr[T] = { 
    import c.universe._ 

    val baseType = implicitly[c.WeakTypeTag[T]].tpe 
    val body = for { 
     member <- baseType.declarations if member.isMethod && member.name.decodedName.toString != "$init$" 
     method = member.asMethod 
     params = for {sym <- method.paramLists.flatten} yield q"""${sym.asTerm.name}: ${sym.typeSignature}""" 
     paramsCall = for {sym <- method.paramLists.flatten} yield sym.name 
     methodName = member.asTerm.name.toString 
    } yield { 
     q"""override def ${method.name}(..$params): ${method.returnType} = { println("Method " + $methodName + " was called"); super.${method.name}(..$paramsCall); }""" 
    } 

    c.Expr[T] {q""" { class A extends $baseType { ..$body }; new A } """} 
    } 
} 

Если вы не хотите, чтобы создать экземпляр, но вы хотите добавить протоколирование только для признака, чтобы вы могли подмешать дальше, вы можете сделать это с относительно того же кода, но с использованием аннотаций типа макро рай : http://docs.scala-lang.org/overviews/macros/annotations это позволит вам помечать определение классов и выполнять изменения прямо в определениях


вы могли бы сделать что-то, что вы хотите с Dynamic, но есть улов - вы не можете сделать его из исходного типа, поэтому это не микширование. Динамический режим начинает работать только в случае сбоя проверки типа, поэтому вы не можете смешивать реальный тип (или я не знаю, как это сделать). Реальный ответ, вероятно, потребует макросов (как @AlexeyRomanov предложил в комментариях), но я не уверен, как написать один, возможно, я придумаю это позже. Тем не менее Dynamic может работать для вас, если вы не ищете DI здесь

trait Foo { 
    def bar(x: Int) = 2 * x 
    def baz(y: Int) = 3 * y 
    } 
    import scala.reflect.runtime.{universe => ru} 
    import scala.language.dynamics 

    trait Wrapper[T] extends Dynamic { 
    val inner: T 
    def applyDynamic(name: String)(args: Any*)(implicit tt: ru.TypeTag[T], ct: ClassTag[T]) = { 
     val im = tt.mirror.reflect(inner) 
     val method = tt.tpe.decl(ru.TermName(name)).asMethod 
     println(method) 
     val mm = im.reflectMethod(method) 
     println(s"$name was called with $args") 
     mm.apply(args:_*) 
    } 
    } 

    class W extends Wrapper[Foo] { 
    override val inner: Foo = new Foo() {} 
    } 

    val w = new W // Cannot be casted to Foo 
    println(w.bar(5)) // Logs a call and then returns 10 

Вы можете прочитать больше о Dynamic здесь: https://github.com/scala/scala/blob/2.12.x/src/library/scala/Dynamic.scala

+0

Интересно! Мне немного странно, что это не позволяет компилятору (улавливать ошибки метода) сравнивать: 'Foo.bar()' и 'w.bar()' или 'Foo.notfound' и' w.notfound'. Я думаю, это означает, что это решение не сработает. +1 в любом случае (я не знал, что вы можете это сделать). –

+0

@ AndyHayden посмотрите пожалуйста. Это требует некоторой дополнительной работы, но я надеюсь, что это отправная точка для вас – Archeg

Смежные вопросы