我给你一个在宏扩展期间利用qoutes.reflect 的解决方案。
使用 qoutes.reflect 可以检查传递的表达式。在我们的例子中,我们希望找到字段名称以便访问它(有关 AST 表示的一些信息,您可以阅读文档here)。
所以,首先,我们需要构建一个内联 def 以便使用宏扩展表达式:
inline def printFields[A](elem : A): Unit = ${printFieldsImpl[A]('elem)}
在实现中,我们需要:
要访问对象字段(仅适用于案例类),我们可以使用对象Symbol,然后使用方法case fields。它为我们提供了一个 List 填充每个案例字段的 Symbol 名称。
然后,要访问字段,我们需要使用Select(由反射模块提供)。它接受一个术语和访问器符号。所以,例如,当我们写这样的东西时:
Select(term, field)
就像写代码一样:
term.field
最后,要打印每个字段,我们只能利用拼接。
总结一下,产生你需要的代码可能是:
import scala.quoted.*
def getPrintFields[T: Type](expr : Expr[T])(using Quotes): Expr[Any] = {
import quotes.reflect._
val fields = TypeTree.of[T].symbol.caseFields
val accessors = fields.map(Select(expr.asTerm, _).asExpr)
printAllElements(accessors)
}
def printAllElements(list : List[Expr[Any]])(using Quotes) : Expr[Unit] = list match {
case head :: other => '{ println($head); ${ printAllElements(other)} }
case _ => '{}
}
所以,如果你把它用作:
case class Dog(name : String, favoriteFood : String, age : Int)
Test.printFields(Dog("wof", "bone", 10))
控制台打印:
wof
bone
10
在@koosha的评论之后,我尝试按字段类型扩展示例选择方法。再次,我使用了宏(对不起:(),我不知道如何选择属性字段而不反映代码。如果有一些提示,欢迎:)
所以,除了第一个例子,在这种情况下,我使用显式类型类从字段中调用和类型。
我创建了一个非常基本的类型类:
trait Show[T] {
def show(t : T) : Unit
}
还有一些实现:
implicit object StringShow extends Show[String] {
inline def show(t : String) : Unit = println("String " + t)
}
implicit object AnyShow extends Show[Any] {
inline def show(t : Any) : Unit = println("Any " + t)
}
AnyShow 被认为是故障安全默认值,如果在隐式解析期间没有找到其他隐式,我使用它来打印元素。
可以使用TypeRep和TypeIdent获取字段类型
val typeRep = TypeRepr.of[T]
val fields = TypeTree.of[T].symbol.caseFields
val fieldsType = fields.map(typeRep.memberType)
.map(_.typeSymbol)
.map(symbol => TypeIdent(symbol))
.map(_.tpe)
.map(_.asType)
现在,给定字段并利用 Expr.summon[T],我可以选择要使用的 Show 实例:
val typeMirror = TypeTree.of[T]
val typeRep = TypeRepr.of[T]
val fields = TypeTree.of[T].symbol.caseFields
val fieldsType = fields.map(typeRep.memberType)
.map(_.typeSymbol)
.map(symbol => TypeIdent(symbol))
.map(_.tpe)
.map(_.asType)
fields.zip(fieldsType).map {
case (field, '[t]) =>
val result = Select(expr.asTerm, field).asExprOf[t]
Expr.summon[Show[t]] match {
case Some(show) =>
'{$show.show($result)}
case _ => '{ AnyShow.show($result) }
}
}.fold('{})((acc, expr) => '{$acc; $expr}) // a easy way to combine expression
然后,您可以将其用作:
case class Dog(name : String, favoriteFood : String, age : Int)
printFields(Dog("wof", "bone", 10))
此代码打印:
String wof
String bone
Any 10