使用Shapeless Lens为Case Class创建通用函数遇隐式值缺失问题
Shapeless Lens通用更新函数编译失败问题
问题描述
尝试用Shapeless Lens为case class创建通用更新函数时编译失败,报错:
could not find implicit value for parameter mkLens: shapeless.MkSelectDynamicOptic
报错代码
import shapeless._ case class MyClass(a: String, b:String, c:Boolean) val cl = MyClass("test", "test", false) def update[T](fieldName: String, value:T): MyClass = { val l = lens[MyClass].selectDynamic(fieldName) l.set(cl)(value) } def updateA(value:String): MyClass = { update[String]("a", "new test") }
可正常运行的对比代码
def updateB(value: String): MyClass = { val l = lens[MyClass].selectDynamic("b") l.set(cl)(value) }
用户明确问题根源是编译器无法推导对应类型T的隐式值,询问是否可自定义隐式实例,或更优的Shapeless修改类方案。
问题原因与解决方案
核心问题是selectDynamic依赖的MkSelectDynamicOptic隐式实例,在通用函数update[T]中无法被编译器推导:因为fieldName是运行时字符串,编译器没法在编译期关联字段名与类型T,自然找不到匹配的隐式透镜实例。
方案1:类型安全的通用更新(推荐)
放弃字符串传参,用Shapeless的LabelledGeneric和类型标签实现类型安全的通用更新,让编译器在编译期校验字段与类型的匹配性:
import shapeless._ import shapeless.labelled._ case class MyClass(a: String, b: String, c: Boolean) val cl = MyClass("test", "test", false) // 通用更新函数,依赖字段的类型标签 def update[FieldName <: Symbol, FieldType, Repr, UpdatedRepr]( obj: MyClass, value: FieldType )(implicit gen: LabelledGeneric.Aux[MyClass, Repr], updateOp: ops.record.Update.Aux[Repr, FieldName, FieldType, UpdatedRepr], witness: Witness.Aux[FieldName] ): MyClass = gen.from(updateOp(gen.to(obj), witness.value -> value)) // 使用示例 val updatedA = update(cl, "new test")(Witness.`'a`) val updatedC = update(cl, true)(Witness.`'c`)
方案2:手动提供隐式实例(不推荐)
如果坚持用字符串传参,需要为每个字段+类型组合手动提供MkSelectDynamicOptic隐式实例,但这会失去通用函数的意义,且易出错:
import shapeless._ case class MyClass(a: String, b: String, c: Boolean) val cl = MyClass("test", "test", false) // 手动为各字段的对应类型提供隐式透镜 implicit val lensA: MkSelectDynamicOptic[MyClass, String] = MkSelectDynamicOptic[MyClass](lens[MyClass].a) implicit val lensB: MkSelectDynamicOptic[MyClass, String] = MkSelectDynamicOptic[MyClass](lens[MyClass].b) implicit val lensC: MkSelectDynamicOptic[MyClass, Boolean] = MkSelectDynamicOptic[MyClass](lens[MyClass].c) // 通用函数可正常编译 def update[T](fieldName: String, value:T)(implicit lens: MkSelectDynamicOptic[MyClass, T]): MyClass = { lens.optic.set(cl)(value) } def updateA(value: String): MyClass = update("a", value)
方案3:直接生成字段Lens封装更新
如果不需要完全通用的函数,可直接为每个字段生成Lens,再封装更新逻辑:
import shapeless._ case class MyClass(a: String, b: String, c: Boolean) val cl = MyClass("test", "test", false) // 生成各字段的Lens val aLens = lens[MyClass].a val bLens = lens[MyClass].b val cLens = lens[MyClass].c // 封装更新函数 def updateA(value: String): MyClass = aLens.set(cl)(value) def updateC(value: Boolean): MyClass = cLens.set(cl)(value)
总结
优先选择方案1的类型安全实现,既保证通用性,又能通过编译器校验避免运行时错误。字符串传参的方式与Shapeless的类型安全理念冲突,不推荐在生产代码中使用。
内容的提问来源于stack exchange,提问作者user2400484
相关产品推荐
相关产品推荐

