Макросы Scala: создание карты из полей класса в Scala
предположим, что у меня много подобных классов данных. Вот пример класса 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 {
...
}
затем я определил реализацию макроса в отдельном
2 ответов
обратите внимание, что это можно сделать гораздо более элегантно без 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
.
есть отличный блог на карте в/из преобразования класса case с помощью макросов.