斯卡拉:宏从一个类主体创建一个实例(Scala: macro to create an instan

2019-10-28 13:02发布

我建立一个DSL在Scala中,为此,我需要存储一个类(的“实例” Parent在这种情况下),除了这些“情况”在运行时,必须重新创建几次。 所以不是我存储“构造函数” - 拉姆达,使实例。

考虑下面的代码-想象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调用应该返回理想的包装物是供参考。

Answer 1:

不幸的是宏也无济于事。

宏说明(类型检查之前,其展开) 不能注释的代码块 :

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

是非法的。

高清宏(其类型检查期间扩大) 有他们的论据类型检查,宏展开之前 。 所以

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


Answer 2:

如果我理解正确的,你只需要改变object ,以defObjectA块:

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

  construct(makeParent)
}

它会做你想要什么。



文章来源: Scala: macro to create an instance from a class body