使用存储为通配符 classTag 的类型调用模板 Scala 函数?

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

我定义了以下 class 来存储对象的 class 标签:

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

和如下模板函数:

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

预期用途如下:

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

这段代码显然行不通,因为模板类型需要在编译时知道。 但是是否有解决方法来实现预期用途?

你的问题听起来像 XY problem。您应该提供有关您实际想要做什么的更多详细信息。标准的 Scala 代码可能就足够了(也许有转换),或者宏就足够了(如果事情可以在编译时完成),或者运行时反射就足够了(至少没有反射编译)。

我假设您实际上仅在运行时拥有有关类型 A, BClassTag[A]ClassTag[B])的信息,并且方法 workOnThis 实际上表现不同,具体取决于 A, B.

看起来很奇怪,但原则上你可以这样做:

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)