如何标准化`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
(p
、q≠0
为任意整数)。这是一个等价关系。所以众所周知,有一个函数 select 从任何等价 class 中获取单个实数,但是如何为特定输入显式定义此函数?例如,对于输入为 0
或 1
或 π
或 e
或 √2
的等价 class,此函数的输出是什么或 log 2
...?
同理,=:=
是类型上的等价关系,所以已知有一个函数normalize
(也许还有很多这样的函数)selecting一个代表在每个等价 class 但如何更喜欢特定的(如何为任何特定输入明确定义或构造输出)?
关于你与隐性分歧的斗争。您不必 select 找到最好的方法。听起来您正在手动执行一些编译器工作。其他 json 图书馆如何解决这个问题?例如 Circe?除了名称隐式 =>
之外,还有 shapeless.Lazy
/ shapeless.Strict
(不等同于名称隐式)。如果您有关于派生类型 classes 的具体问题,克服隐式分歧,也许您应该就此开始一个不同的问题?
关于您使用 HashMap
和 Type
键的方法。我仍然提醒我们不应该依赖 ==
来获得 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
.
如何实现函数 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
(p
、q≠0
为任意整数)。这是一个等价关系。所以众所周知,有一个函数 select 从任何等价 class 中获取单个实数,但是如何为特定输入显式定义此函数?例如,对于输入为 0
或 1
或 π
或 e
或 √2
的等价 class,此函数的输出是什么或 log 2
...?
同理,=:=
是类型上的等价关系,所以已知有一个函数normalize
(也许还有很多这样的函数)selecting一个代表在每个等价 class 但如何更喜欢特定的(如何为任何特定输入明确定义或构造输出)?
关于你与隐性分歧的斗争。您不必 select 找到最好的方法。听起来您正在手动执行一些编译器工作。其他 json 图书馆如何解决这个问题?例如 Circe?除了名称隐式 =>
之外,还有 shapeless.Lazy
/ shapeless.Strict
(不等同于名称隐式)。如果您有关于派生类型 classes 的具体问题,克服隐式分歧,也许您应该就此开始一个不同的问题?
关于您使用 HashMap
和 Type
键的方法。我仍然提醒我们不应该依赖 ==
来获得 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
.