我提供一种解决方案,利用qoutes.reflect
在宏展开期间进行。
使用qoutes.reflect
可以检查传递的表达式。 在我们的情况下,我们想要找到字段名以便访问它(关于AST表示的一些信息,您可以阅读文档这里)。
因此,首先,我们需要构建一个内联def以便通过宏展开扩展表达式:
inline def printFields[A](elem : A): Unit = ${printFieldsImpl[A]('elem)}
在实现时,我们需要:
要访问对象字段(仅适用于 case classes),可以使用对象 Symbol
然后是方法 case fields
。它会给我们一个 List
,其中包含每个case字段的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})
然后,你可以这样使用:
case class Dog(name : String, favoriteFood : String, age : Int)
printFields(Dog("wof", "bone", 10))
这段代码输出:
String wof
String bone
Any 10
productIterator
?最终,这段代码被编译后,通过产品迭代器按名称或索引访问项目是相同的。 - Gaël JproductElement
使用if-then-else
实现,而productIterator
在每一步都调用productElement
实现。我理解这是一个很小的开销。但它仍然是运行时开销 (abc.productElement(1)
没有得到优化)。 - Koosha