找出类型构造函数的类型参数,知道它扩展的类型构造函数的类型参数

Find out the type arguments for a type constructor knowing the type arguments of the type constructor it extends

X 为具有类型参数 A1A2、...、An 的类型构造函数。例如 Option[A]Function1[A1, A2].

X[T1, T2, ..., Tn] 是将类型构造函数 X 应用于具体类型参数 T1T2、... Tn 的类型结果.例如 Option[Int]Function1[Long, List[String]].

YX的直接子类,不固定X的部分类型参数,不增加新的自由类型参数,其类型参数为B1B2、...、Bmm <= n。例如 Some[B]PartialFunction[B1, B2].

我需要实现一个函数来找出要分配给类型参数 R1R2、...、Rm 的具体类型 B1 , B2, ..., Bm 类型构造函数 Y 使得 Y[R1, R2, ..., Rm] <:< X[T1, T2, ..., Tn] 移除所有差异(所有特征和 类 被视为非变体) .

OptionSome 的情况下,很明显 R1 = T1 对于 Some[R1] <:< Option[T1] 移除方差是正确的。 此外,在 Function1PartialFunction 的情况下,显然 R1 = T1R2 = T2 对于 PartialFunction[R1, R2] <:< Function[T1, T2] 移除方差是正确的。 但是对于一般情况来说稍微复杂一些。

鉴于编译器和反射库的 <:< 运算符都必须解决此问题以检查可分配性;我假设反射函数 API 已经解决了我的问题。但我没有找到它。 Y假设它是def asSeenFrom(pre: Type, clazz: Symbol): Type,但我试过了没有用。当然,我做错了什么。

这是回答这个问题的函数的用法示例:

import scala.reflect.runtime.universe._

val optionOfInt: Type = typeOf[Option[Int]]
val someTypeConstructor: ClassSymbol = typeOf[Some[_]].typeSymbol.asClass
val someOfInt: Type = instantiateSubclassTypeConstructor(optionOfInt, someTypeConstructor)

print(someOfInt.typeArgs) // outputs "List(Int)"

其中 instantiateSubclassTypeConstructor 是回答这个问题的函数。

/** @param baseInstantiation a class constructor instantiation. For example: {{{typeOf[Option[Int]]}}}
* @param directSubclassSymbol the [[ClassSymbol] of the class constructor we want to instantiate such that it is assignable to `baseInstantiantion`. For example: {{{typeOf[Some[_]].typeSymbol.asClass}}}
* @return a type instantiation of the class constructor referenced by the `directSubclassSymbol` such that it is assignable to `baseInstantiantion`. For example: {{{typeOf[Some[Int]]}}}
*/
def instantiateSubclassTypeConstructor(baseInstantiation: Type, directSubclassSymbol: ClassSymbol): Type = ???

scala 版本:2.13.3

这是部分答案。它只是找出基classX的类型参数和直接子classY.

的类型参数之间的关系
import scala.reflect.runtime.universe._

/** @param baseType a type resulting of the instantiation of a type constructor. For example: {{{typeOf[Option[Int]]}}}
 * @param directSubclassTypeConstructor the type constructor whose type parameters we want to instantiate. It should be a subclass of the `baseType`'s type constructor.
 * @return the relationship between `baseType`'s type arguments and `directSubclassTypeconstructor`'s type parameters. For example: {{{Map(A -> Int)}}}*/
def typeParametersToBaseTypeArgumentsRelationship(baseType: Type, directSubclassTypeConstructor: Type): Map[Type, Type] = {
    val baseTypeConstructor = baseType.typeConstructor;
    assert(directSubclassTypeConstructor <:< baseTypeConstructor)

    val typeParamsRelationship =
        for {
            (baseTypeParam, baseTypeArgument) <- baseTypeConstructor.typeParams zip baseType.typeArgs
        } yield {
            val directSubclassTypeParam = baseTypeParam.asType.toType.asSeenFrom(directSubclassTypeConstructor, baseType.typeSymbol)
            directSubclassTypeParam -> baseTypeArgument
        }
    typeParamsRelationship.toMap
}

用法示例:

scala> import scala.reflect.runtime.universe._
scala> typeParametersToBaseTypeArgumentsRelationship(
    typeOf[Function1[Long, List[String]]],
    typeOf[PartialFunction[_, _]].typeConstructor
)
val res1: Map[reflect.runtime.universe.Type, reflect.runtime.universe.Type] =
    Map(A -> Long, B -> List[String])

另一个更复杂的用法示例:

sealed trait X[A1, A2, A3]
class Y[B1, B2] extends X[B2, List[B1], B1] {}

scala> typeParametersToBaseTypeArgumentsRelationship(
    typeOf[X[Long, List[String], String]],
    typeOf[Y[_, _]].typeConstructor
)
val res2: Map[reflect.runtime.universe.Type,reflect.runtime.universe.Type] =
    Map(B2 -> Long, List[B1] -> List[String], B1 -> String)

回答问题的缺失部分是创建 directSubclassTypeConstructor 的副本,并根据给定关系实例化类型参数。这需要我没有的知识。

编辑: 找出如何将类型参数应用于类型构造函数后,我能够完成这个答案。

import scala.reflect.runtime.universe._

/** Given a type `baseType` and a type constructor of one of its direct subclasses `directSubclassTypeConstructor`, creates a type by applying said type constructor to the type arguments that were used to create the `baseType` as seen from said direct subclass.
 * @param baseType a type resulting of the instantiation of a type constructor. For example: {{{typeOf[Option[Int]]}}}
 * @param directSubclassTypeConstructor the type constructor we want to instantiate such that it is assignable to `baseType`. For example: {{{typeOf[Some[_]].typeConstructor}}}
 * @return the type constructed by applying the type constructor `directSubclassTypeConstructor` to the type arguments of `baseType` as seen from said type constructor. For example: {{{typeOf[Some[Int]]}}}*/
def applySubclassTypeConstructor(baseType: Type, directSubclassTypeConstructor: Type): Type = {
    val directSubclassTypeParams = directSubclassTypeConstructor.typeParams
    if( directSubclassTypeParams.isEmpty) {
        directSubclassTypeConstructor
    } else {
        val baseTypeConstructor = baseType.typeConstructor;
        assert(directSubclassTypeConstructor <:< baseTypeConstructor)

        val subclassTypeParamsToBaseTypeArgumentsRelationship=
            for {
                (baseTypeParam, baseTypeArgument) <- baseTypeConstructor.typeParams zip baseType.typeArgs
            } yield {
                val directSubclassTypeParam = baseTypeParam.asType.toType.asSeenFrom(directSubclassTypeConstructor, baseType.typeSymbol)
                directSubclassTypeParam -> baseTypeArgument
            }

        val directSubclassTypeArguments =
            for (subclassTypeParm <- directSubclassTypeParams) yield {
                subclassTypeParamsToBaseTypeArgumentsRelationship.find { r =>
                    r._1.typeSymbol.name == subclassTypeParm.name
                }.get._2
            }

        appliedType(directSubclassTypeConstructor, directSubclassTypeArguments)
    }
}

一个简单的用法示例:

scala> applySubclassTypeConstructor(
    typeOf[Option[Int]],
    typeOf[Some[_]].typeConstructor
)
val res1: reflect.runtime.universe.Type =
     Some[Int]

一个复杂的用法示例:

sealed trait X[A1, A2, A3]
class Y[B1, B2] extends X[B2, List[B1], B1] {}

scala> applySubclassTypeConstructor(
    typeOf[X[Long, List[String], String]],
    typeOf[Y[_, _]].typeConstructor
)
val res2: reflect.runtime.universe.Type =
    Y[String,Long]