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) // 现在能正常编译了!
原理说明
这个隐式函数的逻辑很简单:
- 接收一个带标记
Tag的A类型值 - 利用隐式的
A => B转换器(这里就是Timestamp的toLocalDateTime方法,Scala会自动把这个方法转换成函数)得到B类型的值 - 自动给
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
相关产品推荐
相关产品推荐

