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

如何仅基于类型推导嵌套Shapeless Lenses?适配Cats-Mtl实现类透镜

适配cats-mtl的类型驱动Classy Lens实现方案

你正在开发的这个类似Classy Lenses的组件,结合cats-mtl的类型约束来构造Lens,这个方向很贴合类型安全的FP实践!从你给出的代码片段来看,目前已经实现了根Lens和HList元素的Lens,但还需要补充case class的自动推导、嵌套类型支持,以及和cats-mtl的集成逻辑。下面是完整的实现思路:

1. 完善核心Classy类型与自动推导逻辑

首先我们要借助Shapeless的Generic类型类,把case class转换为HList,从而复用HList的Lens推导规则,同时支持嵌套类型的递归推导:

import shapeless._
import shapeless.ops.hlist.Selector

// 核心Classy类型,封装Optic(这里我们先聚焦Lens)
class Classy[O[_, _], S, A](val optic: O[S, A])
object Classy {
  // 隐式查找入口
  def apply[O[_, _], S, A](implicit ev: Classy[O, S, A]): Classy[O, S, A] = ev

  // 根Lens:获取自身的恒等Optic
  implicit def rootLens[S]: Classy[Lens, S, S] = 
    new Classy(OpticDefns.id[S])

  // HList元素的Lens推导:通过Selector定位元素
  implicit def hlistElementLens[S <: HList, A](implicit selector: Selector[S, A]): Classy[Lens, S, A] = {
    val lens = Lens[S, A](_.select(selector))(a => _.updated(selector, a))
    new Classy(lens)
  }

  // Case Class到HList的桥接:利用Generic转换,复用HList的Lens逻辑
  implicit def productClassLens[S, A, Repr <: HList](
    implicit 
    gen: Generic.Aux[S, Repr],
    hlistClassy: Classy[Lens, Repr, A]
  ): Classy[Lens, S, A] = {
    val hlistLens = hlistClassy.optic
    // 转换case class和HList之间的Lens
    val productLens = Lens[S, A](
      s => hlistLens.get(gen.to(s)),
      a => s => gen.from(hlistLens.set(gen.to(s))(a))
    )
    new Classy(productLens)
  }

  // 嵌套类型的Lens推导:递归组合外层和内层的Lens
  implicit def nestedClassLens[S, A, B](
    implicit 
    outer: Classy[Lens, S, A],
    inner: Classy[Lens, A, B]
  ): Classy[Lens, S, B] = {
    val composedLens = outer.optic.compose(inner.optic)
    new Classy(composedLens)
  }
}

2. 与cats-mtl集成:适配MonadState

为了让Classy Lens能直接配合cats-mtl的MonadState使用,我们可以添加扩展方法,把Lens操作映射到MonadState的标准方法:

import cats.mtl.MonadState
import cats.mtl.syntax.all._

// 扩展Classy,提供MonadState相关操作
implicit class ClassyMonadStateOps[S, A](val classy: Classy[Lens, S, A]) {
  // 从状态中提取目标值
  def get[F[_]](implicit ms: MonadState[F, S]): F[A] = 
    ms.inspect(classy.optic.get)

  // 更新状态中的目标值
  def set[F[_]](a: A)(implicit ms: MonadState[F, S]): F[Unit] = 
    ms.modify(classy.optic.set(a))

  // 对状态中的目标值进行转换
  def modify[F[_]](f: A => A)(implicit ms: MonadState[F, S]): F[Unit] = 
    ms.modify(s => classy.optic.modify(f)(s))
}

3. 使用示例

现在你可以轻松地基于类型自动推导Lens,并结合cats-mtl使用:

// 定义嵌套的case class
case class Address(street: String, zipCode: String)
case class User(id: Int, name: String, address: Address)

// 自动推导User到name的Lens
val userNameLens = Classy[Lens, User, String]
// 自动推导User到Address的zipCode的嵌套Lens
val userZipCodeLens = Classy[Lens, User, String]

// 配合MonadState使用的示例
import cats.effect.IO
import cats.mtl.instances.state._

val initialUser = User(1, "Alice", Address("Main St", "12345"))

// 获取用户名
val getName: IO[String] = userNameLens.get[IO].runS(initialUser).map(_._2)
// 更新邮政编码
val updateZip: IO[User] = userZipCodeLens.set("54321").runS(initialUser).map(_._1)

关键注意事项

  • 确保引入正确的Shapeless依赖(比如shapeless-core,版本建议和你的Scala版本匹配)
  • 如果需要支持密封特质的子类选择,可以扩展Coproduct相关的implicit推导规则
  • 若要支持更多Optic类型(比如Prism、Traversal),可以扩展Classy的类型参数约束,添加对应的implicit推导

内容的提问来源于stack exchange,提问作者Oleg Pyzhcov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:54:32