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

Scala 3如何通过宏消除Trait实现类的重复构造代码?

问题背景

有一个Scala trait WithPayload,所有实现它的case class都必须在构造函数里重复声明并初始化trait的字段,代码冗余问题明显:

sealed trait WithPayload:
    def description: String
    def payload1: Int
    def payload2: Long

// 必须手动列出所有WithPayload字段
final case class Foo(
    override val payload1: Int,
    override val payload2: Long
) extends WithPayload:
    override def description = "foo"

// 同样要重复写一遍字段声明
final case class Bar(
    override val payload1: Int,
    override val payload2: Long
) extends WithPayload:
    override def description = "bar"

希望通过Scala宏消除这种重复,实现类似C++宏的效果:

#define EXTENDS_WITH_PAYLOAD ( \
    override val payload1: Int, \
    override val payload2: Long \
) extends WithPayload

从而写出简洁的代码:

final case class Foo EXTENDS_WITH_PAYLOAD:
    override def description = "foo"

final case class Bar EXTENDS_WITH_PAYLOAD:
    override def description = "bar"
解决方案

当然可以,在Scala 3中,你可以通过宏注解或者inline元编程实现这个需求,以下是两种具体实现方式:

方案一:宏注解(最贴近需求语法)

宏注解能在编译期修改类定义,自动为case class添加构造参数和trait继承声明,完全贴合你想要的简洁语法。

1. 实现宏注解

import scala.quoted.*

// 定义注解类
class extendsWithPayload extends scala.annotation.Annotation:
  inline def apply(defn: Any): Any = ${ ExtendsWithPayloadMacro.impl(defn) }

// 宏实现逻辑
object ExtendsWithPayloadMacro:
  def impl(defn: Expr[Any])(using Quotes): Expr[Any] =
    import quotes.reflect.*
    defn match
      // 匹配无参的final case class定义
      case '{ final case class $className() extends $parent: $body } =>
        // 构造需要添加的payload字段参数
        val payload1Param = ValDef.overloaded(
          name = "payload1",
          tpt = TypeTree.of[Int],
          flags = Flags.Val | Flags.Override
        )
        val payload2Param = ValDef.overloaded(
          name = "payload2",
          tpt = TypeTree.of[Long],
          flags = Flags.Val | Flags.Override
        )
        // 修改原类的构造参数和父类
        val updatedClass = ClassDef.copy(className.asInstanceOf[Ident].symbol.asClass)(
          name = className.asInstanceOf[Ident].name,
          tparams = Nil,
          constr = DefDef.copy(Symbol.spliceOwner.primaryConstructor)(
            name = "<init>",
            paramss = List(List(payload1Param, payload2Param)),
            tpt = TypeTree.of[Unit],
            rhs = None
          ),
          parents = List(TypeTree.of[WithPayload]),
          self = None,
          body = body.asInstanceOf[Term].asInstanceOf[Block].stats
        )
        // 返回修改后的类定义表达式
        Expr(updatedClass)
      // 处理不符合要求的注解使用场景
      case _ =>
        report.error("@extendsWithPayload 仅适用于无参final case class")
        defn

2. 使用注解简化代码

现在你可以像这样写代码,编译期宏会自动展开成完整的case class定义:

@extendsWithPayload
final case class Foo:
  override def description = "foo"

@extendsWithPayload
final case class Bar:
  override def description = "bar"

方案二:inline元编程(生成式定义)

如果能接受稍不同的调用语法,也可以用Scala 3的inline特性实现类的生成:

import scala.quoted.*

inline def createPayloadCaseClass[T](inline desc: String): (Int, Long) => T = 
  ${ createPayloadCaseClassImpl[T](desc) }

object createPayloadCaseClassImpl:
  def createPayloadCaseClassImpl[T](desc: Expr[String])(using Quotes, Type[T]): Expr[(Int, Long) => T] =
    import quotes.reflect.*
    val className = TypeRepr.of[T].typeSymbol.name
    // 定义case class的字段和构造函数
    val payload1Sym = Symbol.newVal(Symbol.spliceOwner, "payload1", TypeRepr.of[Int], Flags.Val | Flags.Override, Symbol.noSymbol)
    val payload2Sym = Symbol.newVal(Symbol.spliceOwner, "payload2", TypeRepr.of[Long], Flags.Val | Flags.Override, Symbol.noSymbol)
    // 构造case class定义
    val classDef = ClassDef(
      Symbol.newClass(Symbol.spliceOwner, className, Nil, selfType = None, Flags.Case | Flags.Final),
      tparams = Nil,
      constr = DefDef(Symbol.newMethod(Symbol.spliceOwner, "<init>", Nil, List(List(payload1Sym, payload2Sym)), TypeRepr.of[Unit], Flags.Method),
        paramss = List(List(ValDef(payload1Sym), ValDef(payload2Sym))),
        tpt = TypeTree.of[Unit],
        rhs = None
      ),
      parents = List(TypeTree.of[WithPayload]),
      self = None,
      body = List(
        // 实现description方法
        DefDef(Symbol.newMethod(Symbol.spliceOwner, "description", Nil, Nil, TypeRepr.of[String], Flags.Method),
          paramss = Nil,
          tpt = TypeTree.of[String],
          rhs = Some(desc.asTerm)
        )
      )
    )
    // 生成伴生对象的apply方法引用
    classDef.symbol.companionModule
    Expr.betaReduce('{ ${Expr(classDef.symbol.companionModule)}.apply })

调用示例

// 生成Foo类的构造器
val fooBuilder = createPayloadCaseClass[Foo]("foo")
val foo = fooBuilder(1, 2L)

val barBuilder = createPayloadCaseClass[Bar]("bar")
val bar = barBuilder(3, 4L)

注意事项

  • 上述方案基于Scala 3实现,Scala 2的宏系统语法差异较大,无法直接复用;
  • 宏注解需要正确配置编译期依赖,通常建议将宏代码放在单独的模块中;
  • 后续如果WithPayload的字段有修改,只需更新宏实现,所有使用注解的类会自动同步,无需逐个修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:07:10