在 Scala 3 的编译时提取和访问字段
Extracting and accessing fields at compile time in Scala 3
在 Scala 3 中的编译时 class 提取案例元素的名称和类型 已在此博客中得到很好的解释:https://blog.philipp-martini.de/blog/magic-mirror-scala3/
但是,同一个博客使用 productElement
来获取实例中存储的值。我的问题是如何直接访问它们?考虑以下代码:
case class Abc(name: String, age: Int)
inline def printElems[A](inline value: A)(using m: Mirror.Of[A]): Unit = ???
val abc = Abc("my-name", 99)
printElems(abc)
您如何(更新 printElems
的签名并)实现 printElems
以便 printElems(abc)
将扩展为如下内容:
println(abc.name)
println(abc.age)
或至少这样:
println(abc._1())
println(abc._2())
但是不是这个:
println(abc.productElement(0))
println(abc.productElement(1))
不用说,我正在寻找适用于任意情况 classes 而不仅仅是 Abc
的解决方案。另外,如果必须使用宏,那也没关系。但请只使用 Scala 3。
我给你一个在宏扩展期间利用 qoutes.reflect
的解决方案。
使用 qoutes.reflect 可以检查传递的表达式。在我们的例子中,我们想要找到字段名称以便访问它(有关 AST 表示的一些信息,您可以阅读文档 here)。
所以,首先,我们需要构建一个内联定义,以便用宏扩展表达式:
inline def printFields[A](elem : A): Unit = ${printFieldsImpl[A]('elem)}
在实现中,我们需要:
- 获取对象中的所有字段
- 访问字段
- 打印每个字段
要访问对象字段(仅适用于案例 classes),我们可以使用对象 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 的评论后,我尝试按字段类型扩展示例 selecting 方法。同样,我使用了宏(抱歉 :( ),我不知道如何在不反映代码的情况下 select 属性字段。如果有一些提示,欢迎 :)
所以,除了第一个示例,在这种情况下,我使用显式类型 classes 召唤并从字段中输入。
我创建了一个非常基本的类型 class:
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],我可以 select 使用 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
在 Scala 3 中的编译时 class 提取案例元素的名称和类型 已在此博客中得到很好的解释:https://blog.philipp-martini.de/blog/magic-mirror-scala3/
但是,同一个博客使用 productElement
来获取实例中存储的值。我的问题是如何直接访问它们?考虑以下代码:
case class Abc(name: String, age: Int)
inline def printElems[A](inline value: A)(using m: Mirror.Of[A]): Unit = ???
val abc = Abc("my-name", 99)
printElems(abc)
您如何(更新 printElems
的签名并)实现 printElems
以便 printElems(abc)
将扩展为如下内容:
println(abc.name)
println(abc.age)
或至少这样:
println(abc._1())
println(abc._2())
但是不是这个:
println(abc.productElement(0))
println(abc.productElement(1))
不用说,我正在寻找适用于任意情况 classes 而不仅仅是 Abc
的解决方案。另外,如果必须使用宏,那也没关系。但请只使用 Scala 3。
我给你一个在宏扩展期间利用 qoutes.reflect
的解决方案。
使用 qoutes.reflect 可以检查传递的表达式。在我们的例子中,我们想要找到字段名称以便访问它(有关 AST 表示的一些信息,您可以阅读文档 here)。
所以,首先,我们需要构建一个内联定义,以便用宏扩展表达式:
inline def printFields[A](elem : A): Unit = ${printFieldsImpl[A]('elem)}
在实现中,我们需要:
- 获取对象中的所有字段
- 访问字段
- 打印每个字段
要访问对象字段(仅适用于案例 classes),我们可以使用对象 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 的评论后,我尝试按字段类型扩展示例 selecting 方法。同样,我使用了宏(抱歉 :( ),我不知道如何在不反映代码的情况下 select 属性字段。如果有一些提示,欢迎 :)
所以,除了第一个示例,在这种情况下,我使用显式类型 classes 召唤并从字段中输入。
我创建了一个非常基本的类型 class:
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],我可以 select 使用 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