gpt4 book ai didi

scala - Scala宏:使用Scala中的类的字段制作 map

转载 作者:行者123 更新时间:2023-12-03 08:39:35 25 4
gpt4 key购买 nike

假设我有很多类似的数据类。这是一个示例类User,其定义如下:

case class User (name: String, age: Int, posts: List[String]) {
val numPosts: Int = posts.length

...

def foo = "bar"

...
}

我感兴趣的是自动创建一种方法(在编译时为 ),该方法以在运行时调用时将每个字段名称映射为其值的方式返回 Map。对于上面的示例,假设我的方法称为 toMap:
val myUser = User("Foo", 25, List("Lorem", "Ipsum"))

myUser.toMap

应该回来
Map("name" -> "Foo", "age" -> 25, "posts" -> List("Lorem", "Ipsum"), "numPosts" -> 2)

您将如何使用宏?

这是我做的事情:首先,我创建了 Model类作为所有数据类的父类(super class),并在其中实现了该方法,如下所示:
abstract class Model {
def toMap[T]: Map[String, Any] = macro toMap_impl[T]
}

class User(...) extends Model {
...
}

然后,我在一个单独的 Macros对象中定义了一个宏实现:
object Macros {
import scala.language.experimental.macros
import scala.reflect.macros.Context
def getMap_impl[T: c.WeakTypeTag](c: Context): c.Expr[Map[String, Any]] = {
import c.universe._

val tpe = weakTypeOf[T]

// Filter members that start with "value", which are val fields
val members = tpe.members.toList.filter(m => !m.isMethod && m.toString.startsWith("value"))

// Create ("fieldName", field) tuples to construct a map from field names to fields themselves
val tuples =
for {
m <- members
val fieldString = Literal(Constant(m.toString.replace("value ", "")))
val field = Ident(m)
} yield (fieldString, field)

val mappings = tuples.toMap

/* Parse the string version of the map [i.e. Map("posts" -> (posts), "age" -> (age), "name" -> (name))] to get the AST
* for the map, which is generated as:
*
* Apply(Ident(newTermName("Map")),
* List(
* Apply(Select(Literal(Constant("posts")), newTermName("$minus$greater")), List(Ident(newTermName("posts")))),
* Apply(Select(Literal(Constant("age")), newTermName("$minus$greater")), List(Ident(newTermName("age")))),
* Apply(Select(Literal(Constant("name")), newTermName("$minus$greater")), List(Ident(newTermName("name"))))
* )
* )
*
* which is equivalent to Map("posts".$minus$greater(posts), "age".$minus$greater(age), "name".$minus$greater(name))
*/
c.Expr[Map[String, Any]](c.parse(mappings.toString))
}
}

但是,当我尝试编译该错误时,我从sbt得到了这个错误:
[error] /Users/emre/workspace/DynamoReflection/core/src/main/scala/dynamo/Main.scala:9: not found: value posts
[error] foo.getMap[User]
[error] ^

Macros.scala首先被编译。这是我的Build.scala中的片段:
lazy val root: Project = Project(
"root",
file("core"),
settings = buildSettings
) aggregate(macros, core)

lazy val macros: Project = Project(
"macros",
file("macros"),
settings = buildSettings ++ Seq(
libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _))
)

lazy val core: Project = Project(
"core",
file("core"),
settings = buildSettings
) dependsOn(macros)

我究竟做错了什么?我认为编译器在创建表达式时也会尝试评估字段标识符,但是我不知道如何在表达式中正确返回它们。你能告诉我怎么做吗?

首先十分感谢。

最佳答案

请注意,无需toString / c.parse业务,就可以更加优雅地完成此操作:

import scala.language.experimental.macros

abstract class Model {
def toMap[T]: Map[String, Any] = macro Macros.toMap_impl[T]
}

object Macros {
import scala.reflect.macros.Context

def toMap_impl[T: c.WeakTypeTag](c: Context) = {
import c.universe._

val mapApply = Select(reify(Map).tree, newTermName("apply"))

val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isCaseAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(c.resetAllAttrs(c.prefix.tree), m.name))
reify(name.splice -> value.splice).tree
}

c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))
}
}

还请注意,如果您希望能够编写以下内容,则需要 c.resetAllAttrs位:
User("a", 1, Nil).toMap[User]

如果没有它,在这种情况下,您将得到一个令人困惑的 ClassCastException

顺便说一句,这是我用来避免额外的类型参数的技巧,例如编写如下宏时的 user.toMap[User]:
import scala.language.experimental.macros

trait Model

object Model {
implicit class Mappable[M <: Model](val model: M) extends AnyVal {
def asMap: Map[String, Any] = macro Macros.asMap_impl[M]
}

private object Macros {
import scala.reflect.macros.Context

def asMap_impl[T: c.WeakTypeTag](c: Context) = {
import c.universe._

val mapApply = Select(reify(Map).tree, newTermName("apply"))
val model = Select(c.prefix.tree, newTermName("model"))

val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isCaseAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(model, m.name))
reify(name.splice -> value.splice).tree
}

c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))
}
}
}

现在我们可以编写以下代码:
scala> println(User("a", 1, Nil).asMap)
Map(name -> a, age -> 1, posts -> List())

并且不需要指定我们在谈论 User

关于scala - Scala宏:使用Scala中的类的字段制作 map ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17223213/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com