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

Scala 2.13.3中macro implementation not found问题及scala-logging方案借鉴

解决Scala宏同一编译期调用的"macro implementation not found"问题

问题场景

在Scala 2.13.3中定义RunningStrategies类,通过带可变参数的宏apply方法创建实例,但在同一编译单元调用RunningStrategies(...)时出现错误:

macro implementation not found: apply
(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)

scala-logging的核心解决思路

scala-logging通过拆分宏定义与实际实现载体的方式解决该问题:

  • 宏定义类仅提供宏方法入口,指向宏实现类
  • 宏实现类生成的代码不直接调用宏定义类的构造,而是调用一个普通的非宏实现类
  • 宏定义类通过普通方法包装实现类实例,避免编译期循环依赖

这种设计让编译器在处理宏调用时,能找到已编译的普通实现类,同时避开宏定义与实现的同编译期依赖问题。

适配到RunningStrategies的改造步骤

1. 新增普通实现载体类

创建RunningStrategiesImpl作为实际存储运行策略的普通类,分离宏逻辑与数据存储:

package com.dv.phoenix.brandsafety.models

import com.dv.phoenix.brandsafety.models.RunningStrategy.RunningStrategy

class RunningStrategiesImpl(val runningStrategies: Set[RunningStrategy])

2. 调整RunningStrategies类与伴生对象

将原RunningStrategies改为包装类,提供普通方法供宏生成代码调用:

package com.dv.phoenix.brandsafety.models

import com.dv.phoenix.brandsafety.utils.RunningStrategiesMacros
import scala.language.experimental.macros

case class RunningStrategies private (impl: RunningStrategiesImpl) {
  // 对外暴露策略集合的访问方法
  def runningStrategies: Set[RunningStrategy] = impl.runningStrategies
}

object RunningStrategies {
  // 宏入口方法
  def apply(strategies: RunningStrategy*): RunningStrategies = macro RunningStrategiesMacros.applyImp
  
  // 供宏生成代码调用的普通方法,非宏实现
  private[models] def fromImpl(impl: RunningStrategiesImpl): RunningStrategies = RunningStrategies(impl)
}

3. 修正宏实现逻辑

调整宏实现,让其生成调用普通类的代码,避免直接调用RunningStrategies的私有构造,同时优化策略冲突检查的实现(避免字符串匹配的不可靠性):

package com.dv.phoenix.brandsafety.utils

import com.dv.phoenix.brandsafety.models.{RunningStrategies, RunningStrategy}
import com.dv.phoenix.brandsafety.models.RunningStrategy.RunningStrategy
import scala.reflect.macros.blackbox

object RunningStrategiesMacros {
  def applyImp(c: blackbox.Context)(strategies: c.Expr[RunningStrategy]*): c.Expr[RunningStrategies] = {
    import c.universe._
    
    // 通过符号匹配检查冲突策略,比字符串匹配更可靠
    val inherentSymbol = symbolOf[RunningStrategy.MonitoringAndBlockingInherent.type]
    val recalculateSymbol = symbolOf[RunningStrategy.MonitoringAndBlockingRecalculate.type]
    
    val hasInherent = strategies.exists(_.tree.symbol == inherentSymbol)
    val hasRecalculate = strategies.exists(_.tree.symbol == recalculateSymbol)
    
    if (hasInherent && hasRecalculate) {
      c.abort(
        c.enclosingPosition,
        "runningStrategies cannot include both RunningStrategy.MonitoringAndBlockingInherent and RunningStrategy.MonitoringAndBlockingRecalculate"
      )
    } else {
      // 生成调用普通实现类的代码,再通过fromImpl转为RunningStrategies实例
      c.Expr(q"""
        val impl = new com.dv.phoenix.brandsafety.models.RunningStrategiesImpl(Set(..$strategies))
        com.dv.phoenix.brandsafety.models.RunningStrategies.fromImpl(impl)
      """)
    }
  }
}

4. 使用示例

调用方式保持不变,宏会在编译期完成冲突检查,并生成正确的实例代码:

import com.dv.phoenix.brandsafety.models.{RunningStrategy, RunningStrategies}
import RunningStrategy._

override protected val runningStrategies: RunningStrategies = RunningStrategies(MonitoringOnly, MonitoringAndBlockingInherent)

改造原理说明

  • 宏实现生成的代码仅依赖普通类RunningStrategiesImpl,该类无需宏编译,编译器可在处理宏调用时正常解析
  • RunningStrategies.fromImpl是普通方法,避免了宏直接调用自身构造的循环依赖
  • 符号匹配替代字符串匹配,提升了冲突检查的可靠性与性能

内容的提问来源于stack exchange,提问作者Zvi Mints

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 19:00:23