Узнайте аргументы типа для конструктора типа, зная аргументы типа конструктора типа, который он расширяет

Пусть X будет конструктором типа с параметрами типа A1, A2, ..., An. Например Option[A] и Function1[A1, A2].

Пусть X[T1, T2, ..., Tn] будет типом, полученным в результате применения конструктора типа X к конкретному аргументу типа T1, T2, ... Tn. Например Option[Int] и Function1[Long, List[String]].

Пусть Y будет прямым подклассом X, который оставляет некоторые параметры типа X нефиксированными, не добавляет новый параметр свободного типа, а его параметры типа B1, B2,..., Bm с m <= n. Например Some[B] и PartialFunction[B1, B2].

Мне нужно реализовать функцию, которая находит конкретные типы R1, R2,..., Rm для присвоения параметрам типа B1, B2,..., Bm конструктора типа Y таким образом, что Y[R1, R2, ..., Rm] <:< X[T1, T2, ..., Tn] со всеми удаленными вариантами (все признаки и классы считаются невариантными).

В случае Option и Some ясно, что R1 = T1 для Some[R1] <:< Option[T1] с удаленной дисперсией будет истинным. Кроме того, в случае Function1 и PartialFunction ясно, что R1 = T1 и R2 = 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 = ???

Скала версия: 2.13.3


person Readren    schedule 23.10.2020    source источник
comment
it is clear that R1 = T1 for Some[R1] <:< Option[T1] это правда? Я думаю, что Some[R1] <:< Option[T1] если и только если R1 <:< T1. А вы знаете о стирании шрифта? Я считаю, что то, что вы пытаетесь решить, не может быть решено для общего случая. Только для некоторых очень специфических случаев, когда определения типов сохраняются во время компиляции.   -  person SimY4    schedule 24.10.2020
comment
@ SimY4 То, что я сказал, правильно, потому что я уточнил, убрав дисперсию. Думается, это можно решить. Если я прав, компилятор должен каким-то образом решить эту проблему для вывода типа, а также оператора ‹:‹. Кроме того, если я хорошо помню, я решил это для java много лет назад, и я думаю, что смогу решить это вручную и для scala. Но я предполагаю, что это уже решено в API отражения, и я не могу его найти.   -  person Readren    schedule 24.10.2020
comment
@SimY4 Пожалуйста, посмотрите документацию метода def asSeenFrom(pre: Type, clazz: Symbol): Type, определенного в scala.reflect.api.Types. Видимо решает эту проблему. Но я не могу понять, как его использовать. Пример в документе устарел, поскольку в нем используется ThisType, который был удален из API.   -  person Readren    schedule 24.10.2020
comment
Боковое примечание: я уверен, что есть лучший способ выразить присваиваемое с удаленной дисперсией, поскольку мне не хватает терминологии. Возможно, вопрос станет более ясным, если я заменю это на соответствует.   -  person Readren    schedule 24.10.2020
comment
@Readren Используйте internal.thisType(C) вместо ThisType(C).   -  person Dmytro Mitin    schedule 24.10.2020


Ответы (1)


Это частичный ответ. Он только выясняет взаимосвязь между аргументами типа базового класса X и параметрами типа прямого подкласса Y.

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]
person Readren    schedule 24.10.2020