如何标准化`scala.reflect.api.Types.Type`

how to normalize a `scala.reflect.api.Types.Type`

如何实现函数 normalize(type: Type): Type 使得:

A =:= B 当且仅当 normalize(A) == normalize(B)normalize(A).hashCode == normalize(B).hashCode.

换句话说,normalize 必须 return equal 所有等效的 Type 实例的结果;而不是 equal 也不是所有非等效输入对的等效结果。

TypeApi中有一个被弃用的方法叫做normalize,但它不一样。

在我的特定情况下,我只需要规范化表示 class 或特征 (tpe.typeSymbol.isClass == true) 的类型。

编辑 1: 第一条评论表明这样的功能可能无法在一般情况下实现。但如果我们添加另一个约束,也许是可能的: B是从A导航得到的。 在下一个示例中,fooType 将是 A,而 nextAppliedType 将是 B:

import scala.reflect.runtime.universe._
sealed trait Foo[V]
case class FooImpl[V](next: Foo[V]) extends Foo[V]

scala> val fooType = typeOf[Foo[Int]]
val fooType: reflect.runtime.universe.Type = Foo[Int]

scala> val nextType = fooType.typeSymbol.asClass.knownDirectSubclasses.iterator.next().asClass.primaryConstructor.typeSignature.paramLists(0)(0).typeSignature
val nextType: reflect.runtime.universe.Type = Foo[V]

scala> val nextAppliedType = appliedType(nextType.typeConstructor, fooType.typeArgs)
val nextAppliedType: reflect.runtime.universe.Type = Foo[Int]

scala> println(fooType =:= nextAppliedType)
true

scala> println(fooType == nextAppliedType)
false

showRaw 检查 Type 实例显示了它们不相等的原因(至少当 Foo 和 FooImpl 是对象的成员时,在本例中为 jsfacile.test.RecursionTest 对象) :

scala> showRaw(fooType)
val res2: String = TypeRef(SingleType(SingleType(SingleType(ThisType(<root>), jsfacile), jsfacile.test), jsfacile.test.RecursionTest), jsfacile.test.RecursionTest.Foo, List(TypeRef(ThisType(scala), scala.Int, List())))

scala> showRaw(nextAppliedType)
val res3: String = TypeRef(ThisType(jsfacile.test.RecursionTest), jsfacile.test.RecursionTest.Foo, List(TypeRef(ThisType(scala), scala.Int, List())))

我需要这个的原因很难解释。让我们试试:

我正在开发 this JSON library,它工作正常,除非存在递归类型引用。例如:

sealed trait Foo[V]
case class FooImpl[V](next: Foo[V]) extends Foo[V]

发生这种情况是因为它用于解析和格式化的 parser/appender 是类型 classes,由隐式宏具体化。当隐式参数是递归的时,编译器会抱怨发散错误。 我尝试使用按名称隐式参数来解决这个问题,但它不仅没有解决递归问题,而且还使许多非递归代数数据类型失败。 所以,现在我试图通过将解析的具体化存储在地图中来解决这个问题,这也将提高编译速度。并且该映射键的类型为 Type。所以我需要规范化 Type 个实例,不仅可以用作地图的键,还可以均衡从它们生成的值。

If I understood you well, any equivalence class would be fine. There is no preference.

我怀疑你没有。至少“任何等价物class都可以”“没有偏好”听起来不太好。我会尽量详细说明。

在数学中有因式分解这样的结构。如果你有一个集合 A 和这个集合上的等价关系 ~ (关系意味着对于来自 A 的任何一对元素,我们知道它们是否相关 a1 ~ a2 或不相关,等价意味着对称性a1 ~ a2 => a2 ~ a1、自反性a ~ a、传递性a1 ~ a2, a2 ~ a3 => a1 ~ a3)那么你可以考虑因子集A/~,其元素都是等价的classes A/~ = { [a] | a ∈ A}(等价物class

[a] = {b ∈ A | b ~ a}

的一个元素 a 是一个集合,由与 a 等效(即 ~ 相关)的所有元素组成。

The axiom of choice 表示存在从 A/~A 的映射(函数),即我们可以 select 每个等价的代表 class 和以这种方式形成 A 的子集(如果我们接受选择公理,则为真,如果我们不接受,则不清楚我们是否以这种方式得到一个集合)。但即使我们接受选择公理,因此一个函数A/~ -> A,这并不意味着我们可以构造这样的函数.

Simple example.让我们考虑所有实数的集合R和下面的等价关系:两个实数等价r1 ~ r2如果它们的差是有理数

r2 - r1 = p/q ∈ Q

pq≠0为任意整数)。这是一个等价关系。所以众所周知,有一个函数 select 从任何等价 class 中获取单个实数,但是如何为特定输入显式定义此函数?例如,对于输入为 01πe√2 的等价 class,此函数的输出是什么或 log 2...?

同理,=:=是类型上的等价关系,所以已知有一个函数normalize(也许还有很多这样的函数)selecting一个代表在每个等价 class 但如何更喜欢特定的(如何为任何特定输入明确定义或构造输出)?

关于你与隐性分歧的斗争。您不必 select 找到最好的方法。听起来您正在手动执行一些编译器工作。其他 json 图书馆如何解决这个问题?例如 Circe?除了名称隐式 => 之外,还有 shapeless.Lazy / shapeless.Strict (不等同于名称隐式)。如果您有关于派生类型 classes 的具体问题,克服隐式分歧,也许您应该就此开始一个不同的问题?

关于您使用 HashMapType 键的方法。我仍然提醒我们不应该依赖 == 来获得 Type,正确的比较是 =:=。所以你应该使用 =:= 而不是 == 来构建你的 HashMap。在 SO 搜索类似的东西:hashmap custom equals.


实际上我猜你的 normalize 听起来你想要一些缓存。你应该有一个类型缓存。然后如果你要求计算 normalize(typ) 你应该检查缓存中是否已经有一个 t 使得 t =:= typ。如果是这样你应该 return t,否则你应该添加 typ 到缓存和 return typ.

这满足您的要求:A =:= B 当且仅当 normalize(A) == normalize(B)normalize(A).hashCode == normalize(B).hashCode 应遵循 normalize(A) == normalize(B))。


关于将fooType转换为nextAppliedType试试

def normalize(typ: Type): Type = typ match { 
  case TypeRef(pre, sym, args) =>
    internal.typeRef(internal.thisType(pre.typeSymbol), sym, args) 
}

那么normalize(fooType) == nextAppliedType应该是true.