Shapeless标签记录值提取报错:找不到Selector隐式值
问题分析与解决方案
你的问题在于Witness类型的擦除以及隐式参数无法正确推断出具体的符号类型。让我们一步步拆解问题并修复:
核心问题
- 你声明
val nameWit: Witness = 'name时,nameWit.T被擦除成了抽象的Witness.T,而不是'name对应的具体单例符号类型(Symbol with Singleton)。Shapeless的Selector需要具体的键类型才能匹配LabelledGeneric生成的记录类型中的字段。 Extractor.apply方法的参数k被声明为Witness,没有保留具体的键类型信息,导致编译器无法为Selector找到合适的隐式实例。
修复后的代码
import shapeless._, record._ import shapeless.ops.record.Selector import shapeless.syntax.singleton._ object ShapelessRecordTest extends App { trait Extractor[K, T] { type OUT_K def extract: T => OUT_K } object Extractor { type Aux[K, T, OUT_K0] = Extractor[K, T] { type OUT_K = OUT_K0 } // 修改1:将k的类型改为Witness.Aux[K],保留具体的键类型K implicit def apply[K, T, Repr, OUT](k: Witness.Aux[K])( implicit gen: LabelledGeneric.Aux[T, Repr], selector: Selector.Aux[Repr, K, OUT] ): Aux[K, T, OUT] = new Extractor[K, T] { type OUT_K = OUT def extract: T => OUT_K = (e: T) => { val rep = gen.to(e) // 修改2:使用Shapeless提供的record语法,无需手动创建RecordOps rep.get(k) } } } case class Person(name: String, address: String, age: Int) implicit val gen = LabelledGeneric[Person] // 修改3:保留nameWit的具体符号类型,避免类型擦除 val nameWit = 'name.witness // 现在编译器可以正确推断出Extractor的类型参数 val nameExtractor: Extractor.Aux['name.T, Person, String] = Extractor(nameWit) override def main(args: Array[String]): Unit = { val joe = Person("Joe", "Brighton", 33) println(nameExtractor.extract(joe)) // 输出: Joe } }
关键修改说明
- Witness类型具体化:使用
'name.witness生成Witness.Aux['name.T]类型的实例,而不是擦除为Witness。这样K类型会被保留为具体的'name符号类型,让Selector能匹配LabelledGeneric[Person]生成的记录中的对应字段。 - Extractor.apply的类型参数调整:将
k的类型从Witness改为Witness.Aux[K],确保编译器能捕获到具体的键类型K,从而正确查找Selector隐式实例。 - 简化Record操作:使用
rep.get(k)或者直接rep(k)(借助Shapeless的隐式转换)来获取字段值,无需手动实例化RecordOps。
额外优化
你甚至可以进一步简化代码,直接在使用Extractor时省略显式的类型声明,让编译器自动推断:
val nameExtractor = Extractor('name.witness) // 编译器会自动推断出nameExtractor: Extractor.Aux['name.T, Person, String]
这样就能正常运行并实现你想要的基于类型的字段提取和流程分发了。
内容的提问来源于stack exchange,提问作者codeFun
相关产品推荐
相关产品推荐

