Calling method via reflection in Scala

3.5k Views Asked by At

I want to call an arbitrary public method of an arbitrary stuff via reflection. I.e. let's say, I want to write method extractMethod to be used like:

class User { def setAvatar(avatar: Avatar): Unit = …; … }

val m = extractMethod(someUser, "setAvatar")
m(someAvatar)

From the Reflection. Overview document from Scala docs, I see the following direct way to do that:

import scala.reflect.ClassTag
import scala.reflect.runtime.universe._

def extractMethod[Stuff: ClassTag: TypeTag](
  stuff:      Stuff,
  methodName: String): MethodMirror =
  {
    val stuffTypeTag = typeTag[Stuff]
    val mirror = stuffTypeTag.mirror
    val stuffType = stuffTypeTag.tpe
    val methodSymbol = stuffType
      .member(TermName(methodName)).asMethod
    mirror.reflect(stuff)
      .reflectMethod(methodSymbol)
  }

However what I'm bothered with this solution is that I need to pass implicit ClassTag[Stuff] and TypeTag[Stuff] parameters (first one is needed for calling reflect, second one — for getting stuffType). Which may be quite cumbersome, especially if extractMethod is called from generics that are called from generics and so on. I'd accept this as necessity for some languages that strongly lack runtime type information, but Scala is based on JRE, which allows to do the following:

def extractMethod[Stuff](
  stuff:          Stuff,
  methodName:     String,
  parameterTypes: Array[Class[_]]): (Object*) => Object =
    {
      val unboundMethod = stuff.getClass()
        .getMethod(methodName, parameterTypes: _*)
      arguments => unboundMethod(stuff, arguments: _*)
    }

I understand that Scala reflection allows to get more information that basic Java reflection. Still, here I just need to call a method. Is there a way to somehow reduce requirements (e.g. these ClassTag, TypeTag) of the Scala-reflection-based extractMethod version (without falling back to pure-Java reflection), assuming that performance doesn't matter for me?

1

There are 1 best solutions below

0
On

Yes, there is.

  1. First, according to this answer, TypeTag[Stuff] is a strictly stronger requirement than ClassTag[Stuff]. Although we don't automatically get implicit ClassTag[Stuff] from implicit TypeTag[Stuff], we can evaluate it manually as ClassTag[Stuff](stuffTypeTag.mirror.runtimeClass(stuffTypeTag.tpe)) and then implicitly or explicitly pass it to reflect that needs it:

    import scala.reflect.ClassTag
    import scala.reflect.runtime.universe._
    
    def extractMethod[Stuff: TypeTag](
      stuff:      Stuff,
      methodName: String): MethodMirror =
      {
        val stuffTypeTag = typeTag[Stuff]
        val mirror = stuffTypeTag.mirror
        val stuffType = stuffTypeTag.tpe
        val stuffClassTag = ClassTag[Stuff](mirror.runtimeClass(stuffType))
        val methodSymbol = stuffType
          .member(TermName(methodName)).asMethod
        mirror.reflect(stuff)(stuffClassTag)
          .reflectMethod(methodSymbol)
      }
    
  2. Second, mirror and stuffType can be obtained from stuff.getClass():

    import scala.reflect.ClassTag
    import scala.reflect.runtime.universe._
    
    def extractMethod(stuff: Stuff, methodName: String): MethodMirror = {
      val stuffClass = stuff.getClass()
      val mirror = runtimeMirror(stuffClass.getClassLoader)
      val stuffType = mirror.classSymbol(stuffClass).toType
      val stuffClassTag = ClassTag[Stuff](mirror.runtimeClass(stuffType))
      val methodSymbol = stuffType
        .member(TermName(methodName)).asMethod
      mirror.reflect(stuff)(stuffClassTag)
        .reflectMethod(methodSymbol)
    }
    

Therefore we obtained Scala-style reflection entities (i.e. finally MethodMirror) without requiring ClassTag and/or TypeTag to be passed explicitly or implicitly from the caller. Not sure, however, how it compares with the ways described in the question (i.e. passing tags from outside and pure Java) in the terms of performance.