如何在Scala中反映对应于抽象类型的类型参数的具体类型?

How to reflect concrete types that corresponds to the type parameters of an abstraction type in Scala?

假设我们有一个泛型(例如,Seq[E])和一个具体的子类型(例如,Seq[Int])。我们如何提取与抽象类型的类型参数对应的具体类型。也就是说,我们怎么才能知道E -> Int.

下面是测试所需行为的最小代码示例。 extractTypeBinding 函数将执行所讨论的转换。

import scala.reflect.runtime.{universe => ru}

class MyFuncs

object MyFuncs {
  def fn1[E](s: Seq[E]): E = ???
  def fn2[K, V](m: Map[K, V]): Int = ???
}

object Scratch {

  def extractTypeBinding(genType: ru.Type, typeParam: ru.Type)(concreteType: ru.Type): ru.Type = ???

  def getArgTypes(methodSymbol: ru.MethodSymbol): Seq[ru.Type] =
    methodSymbol.paramLists.headOption.getOrElse(Nil).map(_.typeSignature)

  def main(a: Array[String]): Unit = {

    // Grab the argument types of our methods.
    val funcsType = ru.typeOf[MyFuncs].companion
    val fn1ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn1")).asMethod)
    val fn2ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn2")).asMethod)

    val genericSeq = fn1ArgTypes.head  // Seq[E]
    val genericMap = fn2ArgTypes.head  // Map[K, V]

    // Create an extractor for the `E` in `Seq[E]`.
    val seqElExtractor = extractTypeBinding(genericSeq, genericSeq.typeArgs.head) _
    // Extractor for the `K` in `Map[K,V]`
    val mapKeyExtractor = extractTypeBinding(genericMap, genericMap.typeArgs.head) _
    // Extractor for the `V` in `Map[K,V]`
    val mapValueExtractor = extractTypeBinding(genericMap, genericMap.typeArgs(1)) _

    println(seqElExtractor(ru.typeOf[Seq[Int]])) // should be Int
    println(seqElExtractor(ru.typeOf[Seq[Map[String, Double]]])) // should be Map[String, Double]
    println(mapKeyExtractor(ru.typeOf[Map[String, Double]])) // should be String
    println(mapKeyExtractor(ru.typeOf[Map[Int, Boolean]])) // should be Int
    println(mapValueExtractor(ru.typeOf[Map[String, Double]])) // should be Double
    println(mapValueExtractor(ru.typeOf[Map[Int, Boolean]])) // should be Boolean
  }

}

根据文档字符串,似乎 asSeenFrom 应该是实现 extractTypeBinding 的关键。我尝试了下面的实现,但它返回的类型参数没有改变。

  def extractTypeBinding(genType: ru.Type, typeParam: ru.Type)(concreteType: ru.Type): ru.Type =
    typeParam.asSeenFrom(concreteType, genType.typeSymbol.asClass)
  
  ...

  println(seqElExtractor(ru.typeOf[Seq[Int]])) // E
  println(seqElExtractor(ru.typeOf[Seq[Map[String, Double]]])) // E

如果asSeenFrom是正确的方法,那么正确的咒语是什么? 如果不是,那应该怎么做?

最简单的解决方案来自评论中 Dmytro Mitin 的有用提示。

我对 .typeArgs 有一些误解,这些误解通过一些额外的实验得以消除。

  1. 它 returns 所有类型参数,而不仅仅是抽象参数。
  2. 它只是 returns 您调用它的类型的“顶级”类型参数。换句话说,Map[A, Map[B, C]] 只有 2 个类型参数(AMap[B, C]

这两个现在看起来都很直观,但我最初做了一些愚蠢的假设。下面是我测试的修改版本,更清楚地实现了我的初衷。

class MyFuncs

object MyFuncs {
  def fn1[E](s: Seq[E]): E = ???
  def fn2[K, V](m: Map[K, V]): Int = ???
}

object Scratch {

  def typeArgBindings(genericType: ru.Type, concreteType: ru.Type): Map[ru.Type, ru.Type] =
    // @todo consider validating both have the same base type.
    genericType.typeArgs.zip(concreteType.typeArgs).toMap

  def getArgTypes(methodSymbol: ru.MethodSymbol): Seq[ru.Type] =
    methodSymbol.paramLists.headOption.getOrElse(Nil).map(_.typeSignature)

  def main(a: Array[String]): Unit = {

    // Grab the argument types of our methods.
    val funcsType = ru.typeOf[MyFuncs].companion
    val fn1ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn1")).asMethod)
    val fn2ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn2")).asMethod)

    val genericSeq = fn1ArgTypes.head  // Seq[E]
    val genericMap = fn2ArgTypes.head  // Map[K, V]

    println(typeArgBindings(genericSeq, ru.typeOf[Seq[Int]]))  // Map(E -> Int)
    println(typeArgBindings(genericSeq, ru.typeOf[Seq[Map[String, Double]]]))  // Map(E -> Map[String,Double])
    println(typeArgBindings(genericMap, ru.typeOf[Map[String, Double]]))  // Map(K -> String, V -> Double)
    println(typeArgBindings(genericMap, ru.typeOf[Map[Int, Boolean]]))  // Map(K -> Int, V -> Boolean)

  }

}