You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:37:56