Scala:从类主体创建实例的宏

Scala:从类主体创建实例的宏,scala,metaprogramming,scala-macros,Scala,Metaprogramming,Scala Macros,我正在Scala中构建一个DSL,为此,我需要在本例中存储类父级的实例,但这些实例必须在运行时多次重新创建。因此,我存储的是构造函数——一个生成实例的lambda 考虑以下代码—假设userVar可以在运行时更改,并且在构建实例时必须使用更新的值 class Parent { def func(param: Any): Unit = { ... } } class User { def construct(constr: => Parent): ParentWrapper = {

我正在Scala中构建一个DSL,为此,我需要在本例中存储类父级的实例,但这些实例必须在运行时多次重新创建。因此,我存储的是构造函数——一个生成实例的lambda

考虑以下代码—假设userVar可以在运行时更改,并且在构建实例时必须使用更新的值

class Parent {
  def func(param: Any): Unit = { ... }
}

class User {
  def construct(constr: => Parent): ParentWrapper = { ... }

  var userVar = 13

  construct(new Parent {
    func(1)
    func(userVar)
  }

  construct(new Parent {
    func(userVar)
  }
}
使用上述定义表达我想要的更自然的方式是:

class User {
  var userVar = 13
  object ObjectA extends Parent {
    func(1)
    func(userVar)
  }

  construct(ObjectA)
}
然而,这似乎是不可能的,因为ObjectA是立即创建的,并且没有构造函数

我在想,通过对宏的一些创造性使用,我可以这样做:

class User {
  var userVar = 13

  constructMacro {
    func(1)
    func(userVar}
  }
}
并让constructMacro将代码转换为constructnew Parent{code block goes here}

我该怎么做


还是有更好的方法来避免新的父{…}调用?我的要求是,在用户类中的某个地方存储了一个引用,我可以重复调用并获取父定义的新实例,这些实例反映了在其构造中使用的新值,而构造调用理想情况下应该为该引用返回一个包装器对象。

如果我理解正确,只需在ObjectA块中将object更改为def:


它会做你想做的事。

不幸的是,宏帮不上忙

在类型检查之前展开的宏注释:

这是违法的

在类型检查期间展开的Def宏。所以

不编译:

Error: not found: value func
      func(1)
Error: not found: value func
      func(userVar)
这就是宏接受字符串而不是代码块的原因:

illTyped("""
  val x: Int = "a"
""")
而不是

illTyped {
  val x: Int = "a"
}
因此,您可以实现的最接近的方法是

constructMacro("""
  func(1)
  func(userVar)
""")

def constructMacro(block: String): ParentWrapper = macro constructMacroImpl

def constructMacroImpl(c: blackbox.Context)(block: c.Tree): c.Tree = {
  import c.universe._
  val q"${blockStr: String}" = block
  val block1 = c.parse(blockStr)
  q"""construct(new Parent {
    ..$block1
  })"""
}
可以对变量进行注释

@constructMacro val x = {
  func(1)
  func(userVar)
}

//         becomes
// val x: ParentWrapper = construct(new Parent {
//   func(1)
//   func(userVar)
// })

@compileTimeOnly("enable macro paradise to expand macro annotations")
class constructMacro extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro constructMacroImpl.impl
}

object constructMacroImpl {
  def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods val $tname: $tpt = { ..$stats }" :: Nil =>
        q"$mods val $tname: ParentWrapper = construct(new Parent { ..$stats })"

      case _ =>
        c.abort(c.enclosingPosition, "Not a val")
    }
  }
}

问题是,父对象可以嵌套。一旦进入第一个父级,其他每一个父级都可以是一个类或对象(视情况而定)。我想避免的是必须教导DSL的用户可以使用类或对象,但是根父级是特殊的,必须始终是类,并且必须传递调用新父级的内容来构造。如果只说根是一个裸代码块,并将其转换为引擎盖下的实例,那就更好了。
illTyped {
  val x: Int = "a"
}
constructMacro("""
  func(1)
  func(userVar)
""")

def constructMacro(block: String): ParentWrapper = macro constructMacroImpl

def constructMacroImpl(c: blackbox.Context)(block: c.Tree): c.Tree = {
  import c.universe._
  val q"${blockStr: String}" = block
  val block1 = c.parse(blockStr)
  q"""construct(new Parent {
    ..$block1
  })"""
}
@constructMacro val x = {
  func(1)
  func(userVar)
}

//         becomes
// val x: ParentWrapper = construct(new Parent {
//   func(1)
//   func(userVar)
// })

@compileTimeOnly("enable macro paradise to expand macro annotations")
class constructMacro extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro constructMacroImpl.impl
}

object constructMacroImpl {
  def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods val $tname: $tpt = { ..$stats }" :: Nil =>
        q"$mods val $tname: ParentWrapper = construct(new Parent { ..$stats })"

      case _ =>
        c.abort(c.enclosingPosition, "Not a val")
    }
  }
}