Scala 3技术咨询:如何从Mirror.Sum中提取元素名称并转换为元组?
为什么最初的constValue调用报错?
constValue是编译期求值的工具,它要求上下文必须是inline方法或inline given——只有在inline展开时,编译器才能解析单例类型的常量值。你最初的labelsDeriver没有标记为inline given,编译器无法确认N是编译期可解析的单例类型,因此抛出not a constant type错误。
为什么修改后的代码测试失败?
修改后的代码虽然把given标记为inline,但嵌套的类型类推导存在上下文问题:编译器在推导NamesDeriver.Aux[ElemLabels, ElemLabels]时,无法在mirDeriver的inline上下文中正确展开labelsDeriver的实例,可能 fallback 到emptyDeriver,导致返回空Tuple。
另外,添加NotGiven[ElemLabels =:= EmptyTuple]报错的原因是:Mirror.SumOf[T]的MirroredElemLabels理论上可以是EmptyTuple(比如空的sealed trait),编译器在类型检查阶段无法排除这种可能性,因此无法满足NotGiven约束。
解决方案一:简化实现,直接处理标签Tuple
最简洁的方式是跳过嵌套类型类,直接在MirrorNamesDeriver的inline given中处理标签Tuple:
import scala.deriving.Mirror import scala.compiletime.{constValue, erasedValue} trait MirrorNamesDeriver[T] { type Derived <: Tuple def derive: Derived } object MirrorNamesDeriver { type Aux[T, Ns <: Tuple] = MirrorNamesDeriver[T] { type Derived = Ns } // 辅助inline方法:将标签类型Tuple转换为值Tuple private inline def labelsToTuple[Labels <: Tuple]: Labels = inline erasedValue[Labels] match case _: EmptyTuple => EmptyTuple case _: (head *: tail) => constValue[head] *: labelsToTuple[tail] // 为Sum类型推导标签 inline given sumDeriver[T, ElemLabels <: Tuple]( using mirror: Mirror.SumOf[T] { type MirroredElemLabels = ElemLabels } ): Aux[T, ElemLabels] = new MirrorNamesDeriver[T] { type Derived = ElemLabels def derive: ElemLabels = labelsToTuple[ElemLabels] } // 可选:为Product类型添加支持 inline given productDeriver[T, ElemLabels <: Tuple]( using mirror: Mirror.ProductOf[T] { type MirroredElemLabels = ElemLabels } ): Aux[T, ElemLabels] = new MirrorNamesDeriver[T] { type Derived = ElemLabels def derive: ElemLabels = labelsToTuple[ElemLabels] } inline def getNames[T](using der: MirrorNamesDeriver[T]): der.Derived = der.derive }
测试代码:
sealed trait SuperT final case class SubT1(int: Int) extends SuperT final case class SubT2(str: String) extends SuperT // 测试 val names = MirrorNamesDeriver.getNames[SuperT] println(names.size) // 输出2 println(names) // 输出(SubT1, SubT2)
解决方案二:修复嵌套类型类推导
如果你坚持保留原有的类型类结构,需要确保所有相关given都是inline given,并在inline上下文中推导:
import scala.deriving.Mirror import scala.compiletime.{constValue, erasedValue} trait NamesDeriver[R] { type Derived <: Tuple def derive: Derived } object NamesDeriver { type Aux[R, D <: Tuple] = NamesDeriver[R] { type Derived = D } inline given emptyDeriver: Aux[EmptyTuple, EmptyTuple] = new NamesDeriver[EmptyTuple] { type Derived = EmptyTuple def derive: EmptyTuple = EmptyTuple } inline given consDeriver[N <: String & Singleton, Tail <: Tuple]( using next: Aux[Tail, Tail] ): Aux[N *: Tail, N *: Tail] = new NamesDeriver[N *: Tail] { type Derived = N *: Tail def derive: N *: Tail = constValue[N] *: next.derive } inline def derive[R](using der: NamesDeriver[R]): der.Derived = der.derive } trait MirrorNamesDeriver[T] { type Derived <: Tuple def derive: Derived } object MirrorNamesDeriver { type Aux[T, Ns <: Tuple] = MirrorNamesDeriver[T] { type Derived = Ns } inline given sumDeriver[T, ElemLabels <: Tuple]( using mirror: Mirror.SumOf[T] { type MirroredElemLabels = ElemLabels }, namesDeriver: NamesDeriver.Aux[ElemLabels, ElemLabels] ): Aux[T, ElemLabels] = new MirrorNamesDeriver[T] { type Derived = ElemLabels def derive: ElemLabels = namesDeriver.derive } inline def getNames[T](using der: MirrorNamesDeriver[T]): der.Derived = der.derive }
这个方案保留了类型类的扩展性,同时确保编译器在inline上下文中正确展开每个标签的常量值。
关键总结
constValue必须在inline上下文使用:所有依赖constValue的given或方法都要标记为inline,让编译器在编译期展开时解析常量。- 避免不必要的类型约束:
NotGiven[ElemLabels =:= EmptyTuple]无法工作,因为编译器无法排除空Tuple的可能性,应该允许空Tuple的情况,让emptyDeriver自然处理。 - 优先简化推导逻辑:嵌套类型类推导容易引发上下文问题,直接在顶层inline given中处理Tuple是更可靠的选择。
内容的提问来源于stack exchange,提问作者John Hungerford

