복붙노트

[SCALA] 스칼라 매크로 : 스칼라의 클래스의 필드 밖으로지도 만들기

SCALA

스칼라 매크로 : 스칼라의 클래스의 필드 밖으로지도 만들기

하자 내가 유사한 데이터 클래스를 많이 가지고 있다고 말한다. 여기에 다음과 같이 정의된다 예를 들어 클래스 사용자는 다음과 같습니다

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

  ...

  def foo = "bar"

  ...
}

나는 자동으로 런타임에 호출 될 때 각 필드 이름이 값에 매핑하는 방식으로 맵을 돌려줍니다 (컴파일시에) 방법을 만드는 데 관심이 있습니다. 위의 예를 들어, 내 방법이 toMap이라고 가정 해 봅시다 :

val myUser = User("Foo", 25, List("Lorem", "Ipsum"))

myUser.toMap

반환해야

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

당신은 어떻게 매크로 이런 짓을 했을까?

여기에 내가 한 일입니다 : 첫째, 내 데이터 클래스의 모든 슈퍼 클래스로 모델 클래스를 생성하고 다음과 같이 거기 방법을 구현 :

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

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

그럼 난 별도의 매크로 개체에서 매크로 구현을 정의 :

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)

내가 무엇을 잘못하고 있지? 나는 컴파일러가 식을 만들 때 너무 필드 식별자를 평가하려고 생각,하지만 난 표현에 제대로을 반환하는 방법을 모르겠어요. 당신은 어떻게 그렇게 저를 보여줄 수 있을까?

정말 감사합니다 사전에있다.

해결법

  1. ==============================

    1.이 있으며, toString / c.parse 사업없이 훨씬 더 우아하게 수행 할 수 있습니다 :

    이 있으며, 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 [사용자]과 같은 매크로를 기록 할 때 :

    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())
    

    그리고 우리는 사용자에 대해 얘기하고 있음을 지정할 필요가 없습니다.

  2. ==============================

    2.매크로를 사용하는 경우 클래스 변환에서 /로지도에 우수 블로그 게시물이 있습니다.

    매크로를 사용하는 경우 클래스 변환에서 /로지도에 우수 블로그 게시물이 있습니다.

  3. ==============================

    3.스칼라 2.13부터 (제품의 구현이다)의 경우 클래스는 이제 필드의 이름 반복자를 반환하는 productElementNames 방법으로 제공됩니다.

    스칼라 2.13부터 (제품의 구현이다)의 경우 클래스는 이제 필드의 이름 반복자를 반환하는 productElementNames 방법으로 제공됩니다.

    productIterator 얻은 필드 값 필드 이름을 압축하는 하나는 어떤 경우 클래스에서지도를 얻을 수 있습니다 :

    // val user = User("Foo", 25, List("Lorem", "Ipsum"))
    (user.productElementNames zip user.productIterator).toMap
    // Map[String, Any] = Map("name" -> "Foo", "age" -> 25, "posts" -> List("Lorem", "Ipsum"))
    
  4. from https://stackoverflow.com/questions/17223213/scala-macros-making-a-map-out-of-fields-of-a-class-in-scala by cc-by-sa and MIT license