Scala 3宏实现ToExpr[Foo]时如何构造Expr[Int => Int]实例
错误原因分析
你两次尝试的报错本质都是Scala 3多阶段编程的层级规则限制:
- 尝试1报错:
'{ ... }包裹的引用块属于 staging level 1,而你匹配到的f是当前运行上下文(staging level 0)的普通值,跨层级直接访问被语法禁止。 - 尝试2报错:
$splice操作符只能在引用块内插入Expr[_]类型的值,你传入的f是普通的Int => Int函数,类型不匹配。
解决方案
要实现ToExpr[Foo],核心是将level 0的普通函数值转换为Expr[Int => Int],提供两种可行方案:
方案1:通用序列化方案(支持任意可序列化函数)
Scala的函数字面量默认实现了序列化接口,只要捕获的变量都可序列化,就可以通过序列化+反序列化的方式完成lift,代码如下:
import scala.quoted.* import java.io.* case class Foo(f: Int => Int) given ToExpr[Foo] with def apply(foo: Foo)(using Quotes): Expr[Foo] = // 序列化level 0的函数为字节数组 val byteOut = ByteArrayOutputStream() val objOut = ObjectOutputStream(byteOut) objOut.writeObject(foo.f) objOut.close() val funcBytes = byteOut.toByteArray // 利用标准库内置的Array[Byte] ToExpr实例lift字节数组,再在引用块内反序列化得到函数 val exprF = '{ val byteIn = ByteArrayInputStream(${Expr(funcBytes)}) val objIn = ObjectInputStream(byteIn) objIn.readObject().asInstanceOf[Int => Int] } '{Foo($exprF)}
该方案无需额外适配,适用于大部分业务场景。
方案2:静态函数专属方案(性能更高)
如果你用到的Int => Int函数都是编译时已知的固定逻辑,可以手动实现ToExpr[Int => Int]实例避免序列化开销,示例如下:
import scala.quoted.* case class Foo(f: Int => Int) // 自定义Int=>Int的ToExpr实例,仅适配你用到的函数逻辑 given ToExpr[Int => Int] with def apply(f: Int => Int)(using Quotes): Expr[Int => Int] = f match // 匹配加1函数 case _ if f(0) == 1 && f(100) == 101 => '{ (i: Int) => i + 1 } // 匹配乘2函数 case _ if f(0) == 0 && f(100) == 200 => '{ (i: Int) => i * 2 } // 其他你需要的函数匹配分支 given ToExpr[Foo] with def apply(foo: Foo)(using Quotes): Expr[Foo] = val exprF = Expr(foo.f) // 直接调用上面自定义的ToExpr实例 '{Foo($exprF)}
注意事项
- 序列化方案要求函数及其捕获的所有变量都实现
Serializable接口,Scala原生函数字面量默认满足该要求。 - 静态函数方案性能更高,但只能处理预设范围内的函数逻辑,需要根据业务场景选择。
内容的提问来源于stack exchange,提问作者lepaincestbon
相关产品推荐
相关产品推荐

