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

Shapeless框架下,如何无需重新标记即可修改Tagged Type?

解决Tagged Type转换时自动继承标记的问题

这个场景我太懂了——每次从带标记的Timestamp转成LocalDateTime还要手动加tag[Created],确实有点破坏代码的流畅性。好在我们可以通过Scala的隐式机制,让标记自动“跟着走”,不用每次手动重复标记。

前提假设

先默认你用的是shapeless的Tagged Type实现(这是Scala社区最常用的方案),如果是自定义的Tagged实现,我后面也会补充对应的解决思路。

方案一:通用标记保留转换器

我们可以定义一个通用的隐式转换函数,它会自动把带标记的类型A @@ Tag转换成带相同标记的B @@ Tag,只要A能直接转换成B(比如Timestamp转LocalDateTime的toLocalDateTime方法)。

import shapeless.tag.@@
import java.time.LocalDateTime
import java.sql.Timestamp
import shapeless.tag

// 定义通用的标记保留转换器
implicit def tagPreservingConverter[A, B, Tag](value: A @@ Tag)(implicit converter: A => B): B @@ Tag =
  tag[Tag](converter(value))

定义完这个转换器后,你的转换方法就可以直接写成最初的样子,编译器会自动帮你带上标记:

def toModel(e: EntityRow): Entity = Entity(e.created.toLocalDateTime) // 现在能正常编译了!

原理说明

这个隐式函数的逻辑很简单:

  1. 接收一个带标记Tag的A类型值
  2. 利用隐式的A => B转换器(这里就是Timestamp的toLocalDateTime方法,Scala会自动把这个方法转换成函数)得到B类型的值
  3. 自动给B类型的值加上原来的Tag标记,返回B @@ Tag

这个转换器是通用的,不仅适用于Timestamp转LocalDateTime,任何能互相转换的类型对,只要带相同标记,都能自动套用。

方案二:自定义Tagged实现的适配

如果你的Tagged Type是自己用特质实现的(比如下面这种自定义方案):

trait Tag
type @@[T, U <: Tag] = T with TagWith[U]
trait TagWith[U <: Tag]

def tag[U <: Tag](t: T): T @@ U = t.asInstanceOf[T @@ U]

那可以给带标记的Timestamp定义一个扩展方法,直接返回带相同标记的LocalDateTime:

implicit class TaggedTimestampOps[T <: Tag](ts: Timestamp @@ T) {
  def toTaggedLocalDateTime: LocalDateTime @@ T = tag[T](ts.toLocalDateTime)
}

然后转换方法就可以写成:

def toModel(e: EntityRow): Entity = Entity(e.created.toTaggedLocalDateTime)

这种方式更针对特定类型,但好处是不需要依赖shapeless库。

注意事项

  • 确保隐式转换器/扩展方法的作用域正确:可以把它们放在一个工具对象里,在需要转换的地方import进来,避免隐式冲突。
  • 如果有多个不同的标记类型,这个方案依然有效,因为标记Tag是类型参数化的,不会混淆不同的标记。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:47:00