如何同时获取单例类型的Witness及其嵌套类型的Type Class?
解决Shapeless单例类型与嵌套类型同时推导的问题
这个问题的核心在于,当你同时需要捕获单例类型M(也就是v的具体类型Test.v.type)和它的元素类型L时,Scala编译器没办法自动从M反向推导出L——虽然M是Vector[Int]的子类型,但编译器不会自动把这个关联映射到L=Int上。
为什么f1和f2能工作?
f1只需要捕获单例类型的Witness,编译器可以直接从v -> "foo"的FieldType中提取v的单例类型,所以没问题。f2只需要针对元素类型L的TC[L],编译器可以忽略单例类型,直接从Vector[L]的上层约束中推导出L=Int,并找到对应的intTC实例。
但f3试图同时约束M <: Vector[L]并要求两个隐式,编译器陷入了循环:它需要先知道L才能确认M是否符合Vector[L],但又需要从M推导出L。
解决方案:用辅助类型类关联单例类型与元素类型
我们可以创建一个中间类型类,把Witness[M]和TC[L]绑定在一起,让编译器通过这个类型类一次性推导所需的所有隐式:
import shapeless._ import shapeless.labelled.FieldType import shapeless.syntax.singleton._ sealed trait TC[L] object TC { implicit def intTC = new TC[Int] {} } // 辅助类型类:同时提供单例类型的Witness和元素类型的TC trait VectorWitnessTC[M] { type Elem val witness: Witness.Aux[M] val tc: TC[Elem] } object VectorWitnessTC { // 为所有Vector[E]的子类型(包括单例类型)提供实例 implicit def apply[E, M <: Vector[E]]( implicit tcInstance: TC[E], witnessInstance: Witness.Aux[M] ): VectorWitnessTC[M] = new VectorWitnessTC[M] { type Elem = E val witness = witnessInstance val tc = tcInstance } } object Test { def f1[VI <: Vector[Int]](value: FieldType[VI, String])(implicit wt: Witness.Aux[VI]) = {} def f2[L](value: FieldType[_ <: Vector[L], String])(implicit wt: TC[L]) = {} // 重构后的f3,依赖辅助类型类 def f3[M](value: FieldType[M, String])(implicit vwt: VectorWitnessTC[M]) = { // 在这里可以使用vwt.witness获取单例类型的证据,vwt.tc获取元素类型的TC实例 println(s"单例类型: ${vwt.witness.value}") println(s"元素类型的TC实例: ${vwt.tc}") } val v = Vector(1,2,3) f1(v -> "foo") // 正常运行 f2(v -> "foo") // 正常运行 f3(v -> "foo") // 现在可以正常运行了! }
原理说明
这个辅助类型类VectorWitnessTC做了两件事:
- 它通过类型成员
Elem关联了单例类型M的元素类型。 - 它要求编译器同时找到
M的Witness和Elem的TC实例,通过隐式参数的传递,编译器可以先识别M是Vector[Int]的子类型,进而推导出Elem=Int,并找到对应的intTC和Witness[Test.v.type]。
另一种简化方案:直接添加子类型证据
如果你不想创建额外的类型类,也可以通过显式的子类型证据让编译器明确M和Vector[L]的关系:
def f3[L, M <: Vector[L]](value: FieldType[M, String])(implicit wt: TC[L], witness: Witness.Aux[M], ev: M <:< Vector[L] // 显式告诉编译器M是Vector[L]的子类型 ): Unit = {}
不过这种方式有时候还是会遇到推导问题,因为编译器需要先确定L才能验证ev,而辅助类型类的方式更可靠,因为它把推导逻辑封装到了单独的隐式解析过程中。
内容的提问来源于stack exchange,提问作者thesamet
相关产品推荐
相关产品推荐

