Koo*_*sha 9 scala scala-macros scala-3
在这个博客中已经很好地解释了在 Scala 3 编译时提取案例类的元素的名称和类型: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)
Run Code Online (Sandbox Code Playgroud)
如何(更新和的签名printElems)实现,printElems以便printElems(abc)将其扩展为如下所示:
println(abc.name)
println(abc.age)
Run Code Online (Sandbox Code Playgroud)
或者至少这个:
println(abc._1())
println(abc._2())
Run Code Online (Sandbox Code Playgroud)
但不是这个:
println(abc.productElement(0))
println(abc.productElement(1))
Run Code Online (Sandbox Code Playgroud)
不用说,我正在寻找一种适用于任意 case 类的解决方案,而不仅仅是Abc. 此外,如果必须使用宏,那就没问题了。但请只使用 Scala 3。
我为您提供了宏观扩张期间利用的解决方案qoutes.reflect。
使用 qoutes.reflect 可以检查传递的表达式。在我们的例子中,我们想要找到字段名称以便访问它(有关 AST 表示的一些信息,您可以阅读此处的文档)。
因此,首先,我们需要构建一个内联 def 以便用宏扩展表达式:
inline def printFields[A](elem : A): Unit = ${printFieldsImpl[A]('elem)}
Run Code Online (Sandbox Code Playgroud)
在实施过程中,我们需要:
要访问对象字段(仅适用于案例类),我们可以使用对象Symbol,然后使用方法case fields。它为我们提供了每个案例字段的List名称填充Symbol。
然后,要访问字段,我们需要使用Select(由反射模块给出)。它接受术语和访问器符号。因此,例如,当我们写这样的东西时:
Select(term, field)
Run Code Online (Sandbox Code Playgroud)
就像用代码编写类似的东西:
term.field
Run Code Online (Sandbox Code Playgroud)
最后,要打印每个字段,我们只能利用拼接。总结一下,生成您需要的代码可能是:
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 _ => '{}
}
Run Code Online (Sandbox Code Playgroud)
因此,如果您将其用作:
case class Dog(name : String, favoriteFood : String, age : Int)
Test.printFields(Dog("wof", "bone", 10))
Run Code Online (Sandbox Code Playgroud)
控制台打印:
wof
bone
10
Run Code Online (Sandbox Code Playgroud)
在@koosha的评论之后,我尝试扩展按字段类型选择方法的示例。再次,我使用了宏(抱歉:( ),我不知道如何在不反映代码的情况下选择属性字段。如果有一些提示,欢迎:)
因此,除了第一个示例之外,在本例中,我还使用显式类型类召唤并从字段中键入。
我创建了一个非常基本的类型类:
trait Show[T] {
def show(t : T) : Unit
}
Run Code Online (Sandbox Code Playgroud)
以及一些实现:
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)
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
Show现在,给出该字段并利用 Expr.summon[T],我可以选择要使用的实例:
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
Run Code Online (Sandbox Code Playgroud)
然后,您可以将其用作:
case class Dog(name : String, favoriteFood : String, age : Int)
printFields(Dog("wof", "bone", 10))
Run Code Online (Sandbox Code Playgroud)
此代码打印:
String wof
String bone
Any 10
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
177 次 |
| 最近记录: |