Home > OS >  Invoke a template Scala function with a type stored as wild card classTag?
Invoke a template Scala function with a type stored as wild card classTag?

Time:10-12

I have a following class defined to store the classTag of an object:

case class BindObj (bindTypeA: ClassTag[_], bindTypeB: ClassTag[_]) {
}

and a template function as below:

def workOnThis[A, B](left: A, right: B): Unit => {
    someOtherFunction(A, B)
    ...
}

The intended usage is as follows:

def doWork(left: Anyref, right: Anyref, bindObj: BindObj): Unit => {
    workOnThis[BindObj.bindTypeA, BindObj.bindTypeB](left.asInstance[BindObj.bindTypeA], right[BindObj.bindTypeB])
}

This code obviously doesn't work, because the template types needs to be known in compile time. But is there a workaround to achieve the intended usage?

CodePudding user response:

Your question sounds like XY problem. You should provide more details about what you actually want to do. It's possible that standard Scala code can be enough (maybe with casting), or macros can be enough (if things can be done at compile time), or runtime reflection can be enough (at least without reflective compilation).

I assume that you actually have information about types A, B (ClassTag[A], ClassTag[B]) at runtime only and the method workOnThis actually behaves differently depending on A, B.

It looks weird but in principle you can do the following:

import scala.reflect.api.TypeCreator
import scala.reflect.{ClassTag, api, classTag}
import scala.reflect.runtime.universe.{Quasiquote, Type, TypeTag, weakTypeOf}
import scala.reflect.runtime.{currentMirror => cm}
import scala.tools.reflect.ToolBox

object App {
  val tb = cm.mkToolBox()

  // from (1)
  def backward[T](tpe: Type): TypeTag[T] =
    TypeTag(cm, new TypeCreator {
      override def apply[U <: api.Universe with Singleton](m: api.Mirror[U]): U#Type =
        if (m eq cm) tpe.asInstanceOf[U#Type]
        else throw new IllegalArgumentException(s"Type tag defined in $cm cannot be migrated to other mirrors.")
    })

  def classTagToTypeTag[T](classTag: ClassTag[T]): TypeTag[T] = {
    val symbol = cm.classSymbol(classTag.runtimeClass)
    symbol.typeParams // side effect
    backward(symbol.toType)
  }

  case class BindObj(bindTypeA: ClassTag[_], bindTypeB: ClassTag[_])

  def workOnThis[A, B](left: A, right: B): Unit =
    println(s"A=${weakTypeOf[A]}, B=${weakTypeOf[B]}, left=$left, right=$right")

  def doWork(left: AnyRef, right: AnyRef, bindObj: BindObj): Unit =
    tb.eval(q"""
      App.workOnThis[
        ${classTagToTypeTag(bindObj.bindTypeA)},
        ${classTagToTypeTag(bindObj.bindTypeB)}
      ](_, _)
    """)
    .asInstanceOf[(AnyRef, AnyRef) => Unit]
    .apply(left, right)

  class A
  class B

  def main(args: Array[String]): Unit = {
    doWork(new A, new B, BindObj(classTag[A], classTag[B]))
    // A=A, B=B, left=App$A@75e09567, right=App$B@2a334bac
  }
}

(1) Get a TypeTag from a Type?

  • Related