【问题标题】:Scala: macro to create an instance from a class bodyScala:从类体创建实例的宏
【发布时间】:2019-06-24 11:27:42
【问题描述】:

我正在 Scala 中构建一个 DSL,为此,我需要存储一个类的“实例”(在本例中为 Parent),除了这些“实例”必须在运行时重新创建几次。因此,我存储的是“构造函数”——一个创建实例的 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 将代码转换为construct(new Parent {code block goes here})

我该怎么做?

或者有没有更好的方法来避免尴尬的construct(new Parent{...}) 通话?我的要求是在User 类中的某个位置存储一个引用,我可以重复调用并获取Parent 定义的新实例,这些实例反映了它们构造中使用的新值——并且construct 调用理想情况下应该返回一个该引用的包装对象。

【问题讨论】:

  • 您还可以注释变量(请参阅我的答案更新)。

标签: scala metaprogramming scala-macros


【解决方案1】:

很遗憾,宏无济于事。

宏注解(在类型检查之前扩展)can't annotate code blocks:

@constructMacro {
  func(1)
  func(userVar)
}

是非法的。

Def 宏(在类型检查期间扩展)have their arguments type checked before macros are expanded。所以

constructMacro {
  func(1)
  func(userVar)
}

无法编译:

Error: not found: value func
      func(1)
Error: not found: value func
      func(userVar)

这就是宏 shapeless.test.illTyped 接受字符串而不是代码块的原因:

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")
    }
  }
}

【讨论】:

    【解决方案2】:

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

    class User {
      var userVar = 13
      def makeParent = new Parent {
        func(1)
        func(userVar)
      }
    
      construct(makeParent)
    }
    

    它会做你想做的事。

    【讨论】:

    • 事情是,父对象可以嵌套。一旦你在第一个 Parent 中,其他每个 Parent 都可以是一个类或一个对象,视情况而定。我想避免的是不得不教 DSL 的用户“你可以使用类或对象,但根 Parent 是特殊的,并且必须始终是一个类,你必须将调用 new Parent 的东西传递给 @987654327 @"。最好说根是一个裸代码块,并将其转换为引擎盖下的实例。
    猜你喜欢
    • 2014-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    • 2014-05-31
    • 2014-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多