Scala 3编译时镜像:无类型实例如何获取字段名?
Scala 3 编译时镜像获取无实例case类字段名问题
我们需要将Serializable特质中的fields方法改为无参val,因为字段信息仅依赖类类型,不依赖具体实例。核心问题是如何在没有case类实例的情况下,通过Scala 3编译时镜像获取字段名。
原代码示例
import scala.compiletime.* import scala.deriving.* trait Scalar[T]: def convert(t: T): AnyRef val sqlType: String given Scalar[String] with def convert(t: String) = t val sqlType = "VARCHAR" given Scalar[Int] with def convert(t: Int) = Integer.valueOf(t) val sqlType = "INTEGER" trait Serializable[T]: def serialize(v: T): Map[String, AnyRef] def fields(v: T): Map[String, String] object Serializable: inline def summonAll[T <: Tuple]: List[Scalar[?]] = inline erasedValue[T] match case _: EmptyTuple => Nil case _: (t *: ts) => summonInline[Scalar[t]] :: summonAll[ts] inline given derived[A <: Product](using m: Mirror.ProductOf[A]): Serializable[A] = val scalars = summonAll[m.MirroredElemTypes] new Serializable[A]: override def serialize(v: A): Map[String, AnyRef] = val pro = v.asInstanceOf[Product] pro.productElementNames.zip(pro.productIterator).zip(scalars).map { case ((name, value), scalar) => name -> scalar.asInstanceOf[Scalar[Any]].convert(value) }.toMap override def fields(v: A): Map[String, String] = val pro = v.asInstanceOf[Product] pro.productElementNames.zip(scalars).map { case (name, scalar) => name -> scalar.asInstanceOf[Scalar[Any]].sqlType }.toMap case class Person(name: String, age: Int) derives Serializable
需要修改的Serializable特质
trait Serializable[T]: def serialize(v: T): Map[String, AnyRef] val fields: Map[String, String]
解决方案
Scala 3的编译时镜像Mirror.ProductOf[A]提供了MirroredElemLabels类型,它是一个由字段名字面量组成的元组。我们可以通过constValue在编译时提取这些字面量,无需依赖实例。
修改后的完整代码
import scala.compiletime.* import scala.deriving.* trait Scalar[T]: def convert(t: T): AnyRef val sqlType: String given Scalar[String] with def convert(t: String) = t val sqlType = "VARCHAR" given Scalar[Int] with def convert(t: Int) = Integer.valueOf(t) val sqlType = "INTEGER" trait Serializable[T]: def serialize(v: T): Map[String, AnyRef] val fields: Map[String, String] object Serializable: inline def summonAll[T <: Tuple]: List[Scalar[?]] = inline erasedValue[T] match case _: EmptyTuple => Nil case _: (t *: ts) => summonInline[Scalar[t]] :: summonAll[ts] // 编译时提取字段名列表 inline def getFieldNames[Labels <: Tuple]: List[String] = inline erasedValue[Labels] match case _: EmptyTuple => Nil case _: (label *: labels) => constValue[label] :: getFieldNames[labels] inline given derived[A <: Product](using m: Mirror.ProductOf[A]): Serializable[A] = val scalars = summonAll[m.MirroredElemTypes] // 从镜像中获取编译时字段名 val fieldNames = getFieldNames[m.MirroredElemLabels] // 预先生成fields的Map,无需实例 val fieldsMap = fieldNames.zip(scalars).map { case (name, scalar) => name -> scalar.asInstanceOf[Scalar[Any]].sqlType }.toMap new Serializable[A]: override def serialize(v: A): Map[String, AnyRef] = val pro = v.asInstanceOf[Product] pro.productElementNames.zip(pro.productIterator).zip(scalars).map { case ((name, value), scalar) => name -> scalar.asInstanceOf[Scalar[Any]].convert(value) }.toMap override val fields: Map[String, String] = fieldsMap case class Person(name: String, age: Int) derives Serializable
关键说明
getFieldNames内联方法:通过匹配MirroredElemLabels元组,用constValue[label]提取每个字段名的编译时常量值,最终生成字段名列表。- 预生成
fieldsMap:在derived方法中,直接将编译时获取的字段名与对应的Scalar实例关联,生成fields的Map,无需依赖任何case类实例。 - 类型安全保障:
MirroredElemLabels的元组长度与MirroredElemTypes完全一致,因此fieldNames和scalars的zip操作是类型安全的,编译时即可检查匹配性。
内容的提问来源于stack exchange,提问作者david.perez
相关产品推荐
相关产品推荐

