Note that there are some explanatory texts on larger screens.

plurals
  1. POScala Macros: Making a Map out of fields of a class in Scala
    text
    copied!<p>Let's say that I have a lot of similar data classes. Here's an example class <code>User</code> which is defined as follows:</p> <pre><code>case class User (name: String, age: Int, posts: List[String]) { val numPosts: Int = posts.length ... def foo = "bar" ... } </code></pre> <p>I am interested in automatically creating a method (<strong>at compile time</strong>) that returns a <code>Map</code> in a way that each field name is mapped to its value when it is called in runtime. For the example above, let's say that my method is called <code>toMap</code>:</p> <pre><code>val myUser = User("Foo", 25, List("Lorem", "Ipsum")) myUser.toMap </code></pre> <p>should return</p> <pre><code>Map("name" -&gt; "Foo", "age" -&gt; 25, "posts" -&gt; List("Lorem", "Ipsum"), "numPosts" -&gt; 2) </code></pre> <p>How would you do this with macros? </p> <p>Here's what I have done: First, I created a <code>Model</code> class as a superclass for all of my data classes and implemented the method in there like this:</p> <pre><code>abstract class Model { def toMap[T]: Map[String, Any] = macro toMap_impl[T] } class User(...) extends Model { ... } </code></pre> <p>Then I defined a macro implementation in a separate <code>Macros</code> object:</p> <pre><code>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 =&gt; !m.isMethod &amp;&amp; m.toString.startsWith("value")) // Create ("fieldName", field) tuples to construct a map from field names to fields themselves val tuples = for { m &lt;- 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" -&gt; (posts), "age" -&gt; (age), "name" -&gt; (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)) } } </code></pre> <p>Yet I get this error from sbt when I try to compile it:</p> <pre><code>[error] /Users/emre/workspace/DynamoReflection/core/src/main/scala/dynamo/Main.scala:9: not found: value posts [error] foo.getMap[User] [error] ^ </code></pre> <p>Macros.scala is being compiled first. Here is the snippet from my Build.scala:</p> <pre><code>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 &lt;+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _)) ) lazy val core: Project = Project( "core", file("core"), settings = buildSettings ) dependsOn(macros) </code></pre> <p>What am I doing wrong? I think that the compiler tries to evaluate the field identifiers too when it creates the expression, but I don't know how to return them properly in the expression. Could you show me how to do that?</p> <p>Thanks very much in advance.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload