How to use scala reflection API to get all contain

2020-02-08 09:21发布

问题:

I got this kind of class:

trait ThirdParty { def invoke = println("right") }

trait WeatherIcon { def invoke = println("wrong") }

class MyClass {

    object objA extends ThirdParty

    object objB extends WeatherIcon

}

How can I use the Scala reflection API to iterate through the contained objects and invoke a method if it is an instance of a ThirdParty class?

回答1:

Based on what soc wrote, I got this:

import scala.reflect.runtime.universe._
val members = typeOf[MyClass].members.filter(_.typeSignature match {
  case tpe if tpe <:< typeOf[ThirdParty] => true
  case NullaryMethodType(tpe) if tpe <:< typeOf[ThirdParty] => true
  case MethodType(Nil, tpe) if tpe <:< typeOf[ThirdParty] => true
  case _ => false
})

Let me explain the pattern match. The type of a val or an object can be compared directly, but functions have a slightly different type. Here I'm matching against methods with no parameter lists, and methods with a zero-arity parameter list.

There are some differences here compared to soc's answer. First, I use members instead of declarations. That returns inherited members as well as those that are declared on MyClass itself.

Second, I check that it is a value member, as opposed to a type member. You can only invoke methods on values, so it looked a reasonable restriction, though maybe unnecessary. upd. The isValue method is no longer available in 2.10.0-RC1, so I removed the check.

Finally, I use <:< instead of checking each parent for equality.

Now, to the invocation. I'm going to change the code above since invocation depends on what kind of member you have, so we'd best do filtering and invocation at the same time. I'm going to change from members to nonPrivateMembers as well, assuming that's what is wanted. upd. nonPrivateMembers is no longer available in 2.10.0-RC1, use filter(!_.isPrivate) if necessary.

And I'll also avoid using typeOf, which won't work with mirrors on the REPL. upd. In 2.10.0-RC1 typeOf is working finely, but I'll keep the skeleton of the implementation unchanged.

All of the above is basically concerned with the structure of things: what the members of a type are, what kind of members they are, and so on. When you want to use this stuff, in you need mirrors.

Whenever you have a symbol or a type for something -- a class, method, obj, etc -- you act on that thing through a mirror. To act (reflectively) on an instance of an object, you need an instance mirror. To act on a method, you need a method mirror, and so on.

So let's try to build a functon to do what's requested:

import scala.reflect.runtime.universe._
def invoke[Target : TypeTag](obj: Any): Seq[Target] = {
  val mirror = runtimeMirror(obj.getClass.getClassLoader)
  val insMirror = mirror reflect obj
  val originType = insMirror.symbol.typeSignature
  val targetType = typeTag[Target].tpe

  val members = originType.members

  val result = members collect (member => member.typeSignature match {
    case tpe if tpe <:< typeOf[ThirdParty] =>
      if (member.isModule)
        (insMirror reflectModule member.asModule).instance
      else
        (insMirror reflectField member.asTerm).get
    case NullaryMethodType(tpe) if tpe <:< typeOf[ThirdParty] =>
      (insMirror reflectMethod member.asMethod).apply()
    case MethodType(Nil, tpe) if tpe <:< typeOf[ThirdParty] =>
      (insMirror reflectMethod member.asMethod).apply()
  })

  result.map(_.asInstanceOf[Target]).toSeq
}

Note that nested modules cannot be recovered with Scala 2.10.0-M4 -- that should be possible with M5 or RC1. To test this code with M4, replace the module code with null.

Here's a sample:

scala> class MyClass {
    object objA extends ThirdParty
    object objB extends WeatherIcon
    val aVal = new ThirdParty {}
    val bVal = new WeatherIcon {}
    def aDef = new ThirdParty {}
    def bDef = new WeatherIcon {}
    def anotherDef() = new ThirdParty {}
    def yetAnotherDef() = new WeatherIcon {}
  }
defined class MyClass


scala> invoke[ThirdParty](new MyClass)
res88: Seq[ThirdParty] = List(MyClass$$anon$5@c250cba, MyClass$$anon$3@54668d90, MyClass$$anon$1@18d8143a, null)


回答2:

I can't offer a complete solution, but maybe this is a start:

import reflect.runtime.universe._

val myClassType = typeOf[MyClass]    // Get the type of McClass
val thirdPartyType = typeOf[ThirdParty] // Get the type of ThirdParty
val methodToInvoke = newTermName("invoke")

val declsOfMyClass = myClassType.declarations // Get the declarations of MyClass

val subtypesOfThirdParty = 
  declsOfMyClass.filter(_.typeSignature.parents.contains(thirdPartyType))

val methodsToInvoke =             // Now we have the methods.
  subtypesOfThirdParty.map(tps => tps.typeSignature.member(methodToInvoke))

// TODO: Invoke!

I guess there is a much more straight-forward way than this.