Scala 3 宏:在编译时“动态”实例化单例对象

问题描述 投票:0回答:1

我正在尝试创建一个使用一些

object
的宏。

假设我有以下定义:

trait Foo:
  def doStuff(): Unit

// in other files

object Bar extends Foo:
  def doStuff() = ...

object Qux extends Foo:
  def doStuff() = ...

Foo
不是故意密封的,见下文)

我想创建一个具有以下形状的宏:

inline def runFoo(inline foo: Foo): Unit = ${ runFooImpl('foo) }

这样,当使用

runFoo
的任何单例实例调用
Foo
时,相应的
doStuff
方法将在编译时被调用。

例如:

runFoo(Bar)

将在

编译时
触发Bar.doStuff()

在伪代码中,宏看起来像这样:

def runFooImpl(fooExpr: Expr[Foo]): Expr[Unit] = 
  val foo: Foo = fooExpr.valueOrAbort // does not compile

  foo.doStuff()

 '{ () }

目前

valueOrAbort
无法工作,因为缺少
FromExpr

我的问题是,是否有某种方法可以利用

Bar
Qux
等编译时常量这一事实,以便能够在宏扩展期间从具体的
Expr[Foo]
中提取它们?

请注意,将

Foo
转换为密封特征(并通过模式匹配写入
FromExpr
)不是可接受的解决方案,因为我希望
Foo
可以由客户端代码扩展(限制所有
Foo 
实现必须是
object
s)。

提前致谢

scala scala-macros scala-3
1个回答
0
投票

我以前也这么做过。这种方法需要几个条件:

  • 您应该确保用户实际上会通过

    object
    - 您可以使用例如
    inline def runFoo[A <: Foo & Singleton]: Unit

  • 你不能阻止用户做类似的事情

    class Example1 {
      def localFunction: Unit = {
        object LocalDefinition extends Foo
        runFoo[LocalDefinition]
      }
    }
    
    class Example2 {
      object LocalDefinition extends Foo
      def localFunction: Unit = {  
        runFoo[LocalDefinition]
      }
    }
    

    并且这不能实现为

    object
    - 即使它们是单例 - 包含对封闭类的引用。因此,这仍然需要在宏内部进行检查,并显示编译错误,并显示消息解释原因

  • 一般来说 - 如果您想访问

    object A extends Foo
    ,则宏可用的类路径上必须有
    Class[A$]
    可用

一旦我们接受这些限制,我们可能会一起破解一些东西。几年前我已经制作了类似的原型,并使用我的OSS库中的结果让用户自定义字符串比较的工作方式。

首先起草宏的入口点:

object Macro:

  inline def runStuff[A <: Foo & Singleton](inline a: A): Unit =
    ${ runStuffImpl[A] }
    
  import scala.quoted.*
  def runStuffImpl[A <: Foo & Singleton: Type](using Quotes): Expr[Unit] =
   ???

然后,我们可以实现一段代码,将

Symbol
名称转换为
Class
名称。我将使用不处理嵌套对象的简化版本:

  def summonModule[M <: Singleton: Type](using Quotes): Option[M] =
    val name: String = TypeRepr.of[M].typeSymbol.companionModule.fullName
    val fixedName = name.replace(raw"$$.", raw"$$") + "$"
    try
      Option(Class.forName(fixedName).getField("MODULE$").get(null).asInstanceOf[M])
    catch
      case _: Throwable => None

我们实际上可以在宏中使用该实现(如果可用):

  def runStuffImpl[A <: Foo & Singleton: Type](using Quotes): Expr[Unit] = {
    import quotes.*
    summonModule[A] match
      case Some(foo) =>
        foo.doStuff()
        '{ () }
      case None =>
        reflect.report.throwError(s"${Type.show[A]} cannot be used in macros")
  }

完成。

也就是说,这个宏类型类模式(我称之为宏类型类模式)非常脆弱,容易出错并且对用户来说不直观,所以我建议如果可能的话不要使用它,并且要非常清楚地解释什么样的对象可以去在那里,在文档和错误消息中。

如果你无法通过阅读代码来判断它为什么有效,我也建议不要使用它。

© www.soinside.com 2019 - 2024. All rights reserved.