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

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

关键说明

  1. getFieldNames内联方法:通过匹配MirroredElemLabels元组,用constValue[label]提取每个字段名的编译时常量值,最终生成字段名列表。
  2. 预生成fieldsMap:在derived方法中,直接将编译时获取的字段名与对应的Scalar实例关联,生成fields的Map,无需依赖任何case类实例。
  3. 类型安全保障:MirroredElemLabels的元组长度与MirroredElemTypes完全一致,因此fieldNames和scalars的zip操作是类型安全的,编译时即可检查匹配性。

内容的提问来源于stack exchange,提问作者david.perez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:26:19