[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.이 있으며, 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.매크로를 사용하는 경우 클래스 변환에서 /로지도에 우수 블로그 게시물이 있습니다.
매크로를 사용하는 경우 클래스 변환에서 /로지도에 우수 블로그 게시물이 있습니다.
-
==============================
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"))
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
'SCALA' 카테고리의 다른 글
[SCALA] 스칼라의 애플리케이션 특성과 기본 방법을 사용하여 차이점 (0) | 2019.11.08 |
---|---|
[SCALA] 왜 스칼라 컴파일러 해제하면 기본 인수 방법을 오버로드는 무엇입니까? (0) | 2019.11.08 |
[SCALA] 키에 스파크 dataframes 가입 (0) | 2019.11.08 |
[SCALA] 다양한는 스파크의 유형은 무엇 조인입니까? (0) | 2019.11.08 |
[SCALA] 스칼라의 경우 클래스 상속 (0) | 2019.11.08 |