Scala:从 class 主体创建实例的宏
Scala: macro to create an instance from a class body
我正在 Scala 中构建 DSL,为此,我需要存储 class 的 "instances"(在本例中为 Parent
),除了这些 "instances" 必须在运行时多次重新创建。所以我存储 "constructor functions" - 一个创建实例的 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
是立即创建的,并且没有 "constructor"。
我在想,通过创造性地使用宏,我可以改为这样做:
class User {
var userVar = 13
constructMacro {
func(1)
func(userVar}
}
}
并让 constructMacro
将代码转换为 construct(new Parent {code block goes here})
。
我该怎么做?
或者是否有更好的方法来避免尴尬的 construct(new Parent{...})
调用?我的要求是在 User
class 中的某处存储一个引用,我可以重复调用并获取 Parent
定义的新实例,这些实例反映了在其构造中使用的新值——以及construct
调用最好 return 该引用的包装器对象。
如果我没理解错的话,你只需要把ObjectA
块中的object
改成def
即可:
class User {
var userVar = 13
def makeParent = new Parent {
func(1)
func(userVar)
}
construct(makeParent)
}
它会做你想做的事。
不幸的是宏没有帮助。
宏注释(在类型检查之前展开)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")
}
}
}
我正在 Scala 中构建 DSL,为此,我需要存储 class 的 "instances"(在本例中为 Parent
),除了这些 "instances" 必须在运行时多次重新创建。所以我存储 "constructor functions" - 一个创建实例的 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
是立即创建的,并且没有 "constructor"。
我在想,通过创造性地使用宏,我可以改为这样做:
class User {
var userVar = 13
constructMacro {
func(1)
func(userVar}
}
}
并让 constructMacro
将代码转换为 construct(new Parent {code block goes here})
。
我该怎么做?
或者是否有更好的方法来避免尴尬的 construct(new Parent{...})
调用?我的要求是在 User
class 中的某处存储一个引用,我可以重复调用并获取 Parent
定义的新实例,这些实例反映了在其构造中使用的新值——以及construct
调用最好 return 该引用的包装器对象。
如果我没理解错的话,你只需要把ObjectA
块中的object
改成def
即可:
class User {
var userVar = 13
def makeParent = new Parent {
func(1)
func(userVar)
}
construct(makeParent)
}
它会做你想做的事。
不幸的是宏没有帮助。
宏注释(在类型检查之前展开)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")
}
}
}