Scala:从类主体创建实例的宏 [英] Scala: macro to create an instance from a class body

查看:102
本文介绍了Scala:从类主体创建实例的宏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Scala中构建DSL,为此,我需要存储一个类的实例"(在这种情况下为Parent),除了这些实例"必须在运行时重新创建几次.因此,我改为存储构造函数"-构成实例的lambda.

I am building a DSL in Scala, and for that, I need to store "instances" of a class (Parent in this case), except these "instances" must be re-created several times at runtime. So instead I am storing "constructor functions" - a lambda that makes the instance.

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

consider the following code - imagine that userVar can change at runtime and the updated value must be used when constructing the instances.

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

表达我想要的一种更自然的方式是(使用上面的定义):

A more natural way of expressing what I want would be this (using the above definitions):

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

  construct(ObjectA)
}

但是,鉴于ObjectA是立即创建的并且没有构造函数",因此这似乎是不可能的.

However, that seems impossible, given that ObjectA is created immediately, and doesn't have a "constructor".

我在想,通过创造性地使用宏,我可以改为:

I am thinking, with some creative use of macros, I could instead do this:

class User {
  var userVar = 13

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

并让constructMacro将代码转换为construct(new Parent {code block goes here}).

我该怎么做?

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

Or is there a better way to avoid the awkward construct(new Parent{...}) call? My requirement is that somewhere in the User class a reference is stored that I can repeatedly call and get new instances of the Parent definition that reflect new values used in their construction -- and the construct call should ideally return a wrapper object for that reference.

推荐答案

不幸的是,宏将无济于事.

Unfortunately macros will not help.

宏注释(在类型检查之前会扩展)不能注释代码块:

Macro annotations (which expand before type checking) can't annotate code blocks:

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

是非法的.

Def宏(在类型检查期间扩展)

Def macros (which expand during type checking) have their arguments type checked before macros are expanded. So

constructMacro {
  func(1)
  func(userVar)
}

不编译:

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

这就是为什么宏

That's the reason why macro shapeless.test.illTyped accepts a string rather than code block:

illTyped("""
  val x: Int = "a"
""")

而不是

illTyped {
  val x: Int = "a"
}

因此,您可以实现的最接近的是

So the closest you can implement is

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


您可以注释变量


You can annotate a variable

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

这篇关于Scala:从类主体创建实例的宏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆