Scala 宏:从 Scala 中类的字段中创建映射

2024-01-07

假设我有很多类似的数据类。这是一个示例类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类作为我所有数据类的超类,并在其中实现方法,如下所示:

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

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

然后我在单独的文件中定义了一个宏实现Macros object:

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.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala 宏:从 Scala 中类的字段中创建映射 的相关文章

  • 如何在 Scala 中编写 Pig UDF

    我正在尝试在 Scala 中编写 Pig UDF 使用 Eclipse 我已将 pig jar 添加为 java 构建路径中的库 这似乎解决了以下 2 个导入问题 导入 org apache pig EvalFunc 导入 org apac
  • Spark - scala - 如何检查配置单元中是否存在表

    我必须使用 Spark 1 6 2 scala 检查配置单元中是否存在表 如果没有 我必须创建一个空数据框并将其保存为配置单元表 如果存在 则覆盖现有表 我需要一个返回布尔值的函数 基于该函数我可以做出上述决定 是否创建新表或覆盖现有表 1
  • Scala 中奇怪的类型不匹配

    我希望这个问题还没有在其他地方得到解答 在这里没有找到答案 在我的本地化系统中 我有一个名为 Language 的类 class Language val name String dict HashMap String String def
  • IntelliJ IDEA 13:新的 Scala SBT 项目尚未生成 src 目录结构

    我按照 Jetbrains 网站上的入门视频设置 IntelliJ IDEA 13 1 Community Edition 以与 Scala 配合使用 Scala 插件 v0 36 431 已安装 当我使用向导创建一个新的 Scala SB
  • 过滤字符串上的 Spark DataFrame 包含

    我在用火花1 3 0 http spark apache org releases spark release 1 3 0 html and 火花阿夫罗1 0 0 https github com databricks spark avro
  • 通用 scala 函数,其输入是变量数量的函数

    我想定义一个函数f需要另一个函数g 我们需要g采取采取n双打 对于某些固定n 并返回一个 Double 函数调用f g 应该返回具体值n 例如 f Math max 2因为 Math sin 具有类型 Double Double gt Do
  • Scala(或 Java)中泛型函数的特化

    是否可以在 Scala 中专门化泛型函数 或类 例如 我想编写一个将数据写入 ByteBuffer 的通用函数 def writeData T buffer ByteBuffer data T buffer put data 但由于 put
  • 多个 scala 库导致 intellij 出错?

    我正在使用 intellij 14 和 scala 2 11 6 使用 homebrew 安装并使用符号链接 ln s usr local Cellar scala 2 11 6 libexec src usr local Cellar s
  • Scala 宏的位置怎么了?

    我试图获取宏参数的原始输入字符串 但返回的位置似乎有点偏离 考虑这个宏 例如 object M import scala reflect macros Context import language experimental macros
  • Scala 的代码覆盖率工具 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 模拟 BlazeClientBuilder[IO] 以返回模拟客户端[IO]

    我正在使用BlazeClientBuilder IO resource方法得到Client IO 现在 我想模拟客户端进行单元测试 但不知道该怎么做 有没有一个好的方法来嘲笑这个 我会怎么做 class ExternalCall val r
  • 在 Spark MLlib 上使用 Java 中的 Breeze

    在尝试从Java使用MLlib时 使用微风矩阵运算的正确方法是什么 例如scala 中的乘法很简单 matrix vector 相应的功能在Java中是如何表达的 有一些方法 例如 colon times 可以通过正确的方式调用 breez
  • 规范化且不可变的数据模型

    Haskell如何解决 规范化不可变数据结构 问题 例如 让我们考虑一个表示前女友 男友的数据结构 data Man Man name String exes Woman data Woman Woman name String exes
  • 如何在映射中将字符串转换为 Seq[String]

    我有一个Map String String 以及需要的第三方功能Map String Seq String 有没有一种简单的方法来转换它 以便我可以将地图传递给函数 original mapValues Seq 注意mapValues返回地
  • Akka-Streams 收集数据(Source -> Flow -> Flow (collect) -> Sink)

    我对 Scala 和 Akka 完全陌生 我有一个简单的 RunnableFlow Source gt Flow do some transformation gt Sink runForeach 现在我想要这样的东西 Source gt
  • 应对失败的“未来”

    给出以下两种方法 def f Future Int Future 10 def g Future Int Future 5 我想把它们写成 scala gt import scala concurrent Future import sca
  • 如何在 sbt 控制台中加载 scala 文件? [复制]

    这个问题在这里已经有答案了 可能的重复 将 Scala 文件加载到解释器中以使用函数 https stackoverflow com questions 7383436 load scala file into interpreter to
  • XML 创建 - 错误:带有替代方案的重载方法构造函数 UnprefixedAttribute

    scala gt val count 7 count Int 7 将其放入 XML 属性中会出现错误 scala gt val x
  • Scala 使用的 Redis 客户端库建议

    我正在计划使用 Scala 中的 Redis 实例进行一些工作 并正在寻找有关使用哪些客户端库的建议 理想情况下 如果存在一个好的库 我希望有一个为 Scala 而不是 Java 设计的库 但如果现在这是更好的方法 那么仅使用 Java 客
  • Spark scala 模拟 Spark.implicits 用于单元测试

    当尝试使用 Spark 和 Scala 简化单元测试时 我使用 scala test 和mockito scala 以及mockito Sugar 这只是让你做这样的事情 val sparkSessionMock mock SparkSes

随机推荐