Scala:从类主体创建实例的宏 [英] Scala: macro to create an instance from a class body
问题描述
我正在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 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屋!