在 Scala 3 中查找 lambda 捕获的值(或其类)

Finding lambda captured values (or their classes) in Scala 3

提问人:Andrew 提问时间:12/22/2022 最后编辑:Dmytro MitinAndrew 更新时间:12/24/2022 访问量:121

问:

我正在寻找一种方法来查找 Scala 3 中由 lambda 捕获的值(或其类)(用于序列化 - 类似于 Spark)(我不需要 Scala 2 支持):

val a = "abc"
val f = () => a + "xyz"
serialize(f) // Should detect a / String as captured value

在运行时执行此操作有点容易(迭代),但我想在编译时执行此操作。f.getClass.getDeclaredFields

我试图在 Macro 中检查 lambda 的时间,但它被检测为没有任何有趣信息。scala.Function0

我想知道我是否可以做一些树检查,但我真的很想避免这种情况 - 我觉得我必须复制编译器内部结构才能捕获所有边缘情况。

lambda 闭包 Scala-3 dotty

评论

0赞 Dmytro Mitin 12/22/2022
“在运行时执行此操作有点容易”不知道如何帮助检测.我想输出是一样的,有和没有 scastie.scala-lang.org/DmytroMitin/ZVHSyWTDTduIlTRorzEHIAf.getClass.getDeclaredFieldsaaa
1赞 Dmytro Mitin 12/22/2022
我想在宏中,您可以遍历 lambda 树两次。首次收集定义符号。第二次检查标识符是否引用第一步中的定义。val
1赞 Dmytro Mitin 12/22/2022
因此,您将能够区分 和 .axval f = () => { val x = "uvw"; a + x + "xyz"}
0赞 Andrew 12/23/2022
@DmytroMitin 树解析的问题在于 lambda 之前可能有“import ExternalLibrary.PublicStaticValues.a”。我必须检查是否可以检查变量来自的宏。
1赞 Andrew 12/23/2022
我创建了带有更多用例的测试用例: scastie.scala-lang.org/QknCqi52Qhm67tWLNQId7w - 您的测试用例隐式地位于类构造函数中,它搞砸了一些事情。

答:

1赞 Dmytro Mitin 12/24/2022 #1

请尝试以下

import scala.quoted.*

inline def serialize(x: Any): Unit = ${serializeImpl('x)}

def serializeImpl(x: Expr[Any])(using Quotes): Expr[Unit] = {
  import quotes.reflect.*

  def owners(s: Symbol): List[Symbol] = s :: List.unfold(s)(s1 => Option.when(s1.maybeOwner != Symbol.noSymbol)((s1.maybeOwner, s1.maybeOwner)))

  val symbol = x.asTerm.underlying.symbol
  val rhs = symbol.tree match {
    case ValDef(_, _, Some(rhs)) => rhs
  }

  val traverser = new TreeTraverser {
    override def traverseTree(tree: Tree)(owner: Symbol): Unit = {
      tree match {
        case Ident(name) =>
          val symbol1 = tree.symbol
          val pos1 = symbol1.pos.get
          println(s"identifier: $name, defined inside lambda: ${owners(symbol1).contains(symbol)}, defined in current file: ${pos1.sourceFile == SourceFile.current}")

        case _ =>
      }

      super.traverseTree(tree)(owner)
    }
  }

  traverser.traverseTree(rhs)(rhs.symbol)

  '{()}
}

用法:

object App1 {
  val b = "bbb"
}
import App1.b

object App {
  val a = "abc"
  val f = () => { val x = "uvw"; a + b + x + "xyz"}
  serialize(f)
}

//scalac: identifier: a, defined inside lambda: false, defined in current file: true
//scalac: identifier: b, defined inside lambda: false, defined in current file: false
//scalac: identifier: x, defined inside lambda: true, defined in current file: true
//scalac: identifier: $anonfun, defined inside lambda: true, defined in current file: true

评论

0赞 Andrew 12/27/2022
非常感谢您的回答,不幸的是,由于某种原因,它没有在我的项目中编译,并且似乎绑定到对象范围内。但是,多亏了 method 和 traverseTree,我目前正在研究符合我的项目要求的解决方案。valowners
0赞 Dmytro Mitin 12/28/2022
@Andrew 为了方便起见,我在 github.com/DmytroMitin/scala3-macro-closure-demo 发布了该项目 如果您的编译错误是因为定义右侧的 AST 为空,那么很可能您丢失了 。如果您仍然遇到编译错误并需要帮助,请提供复制(在此问题或新问题中)。MatchErrorvalscalacOptions += "-Yretain-trees"