假設我有很多類似的數據類。下面是其被定義爲如下的示例類User
:Scala宏:在Scala中創建一個類的字段
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 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)
我在做什麼錯?我認爲編譯器在創建表達式時也會嘗試評估字段標識符,但我不知道如何在表達式中正確返回它們。你能告訴我該怎麼做嗎?
非常感謝。
而不是使用宏,這可能會更容易http://stackoverflow.com/questions/1226555/case-class-to-map-in-scala – Noah
@Noah,是的,看到一個已經。但我有興趣在編譯宏時儘管如此。謝謝您的幫助! – Emre
而不只是'訂貨號(newTermName(職位))'你需要使用'選擇(c.prefix.tree,newTermName( 「上崗」))'。 –