[SCALA] 어떻게 스칼라 매크로 메소드 호출에서 명명 된 매개 변수를 모델링하려면?
SCALA어떻게 스칼라 매크로 메소드 호출에서 명명 된 매개 변수를 모델링하려면?
이 공통으로 특정 값을 가질 경우 클래스의 집합의 경우 클래스의 인스턴스 인 객체의 복사본을 만드는 데 유용 경우 사용 사례가 있습니다.
예를 들어의 다음과 같은 경우 클래스를 생각해 보자 :
case class Foo(id: Option[Int])
case class Bar(arg0: String, id: Option[Int])
case class Baz(arg0: Int, id: Option[Int], arg2: String)
그리고이 경우 클래스 인스턴스의 각 호출 할 수 있습니다 복사 :
val newId = Some(1)
Foo(None).copy(id = newId)
Bar("bar", None).copy(id = newId)
Baz(42, None, "baz").copy(id = newId)
여기와 여기에 설명 된 바와 같이이 같은이 추상에 간단한 방법이 없다 :
type Copyable[T] = { def copy(id: Option[Int]): T }
// THIS DOES *NOT* WORK FOR CASE CLASSES
def withId[T <: Copyable[T]](obj: T, newId: Option[Int]): T =
obj.copy(id = newId)
그래서 나는이 일을 (거의) 수행하는 스칼라 매크로를 작성 :
import scala.reflect.macros.Context
object Entity {
import scala.language.experimental.macros
import scala.reflect.macros.Context
def withId[T](entity: T, id: Option[Int]): T = macro withIdImpl[T]
def withIdImpl[T: c.WeakTypeTag](c: Context)(entity: c.Expr[T], id: c.Expr[Option[Int]]): c.Expr[T] = {
import c.universe._
val currentType = entity.actualType
// reflection helpers
def equals(that: Name, name: String) = that.encoded == name || that.decoded == name
def hasName(name: String)(implicit method: MethodSymbol) = equals(method.name, name)
def hasReturnType(`type`: Type)(implicit method: MethodSymbol) = method.typeSignature match {
case MethodType(_, returnType) => `type` == returnType
}
def hasParameter(name: String, `type`: Type)(implicit method: MethodSymbol) = method.typeSignature match {
case MethodType(params, _) => params.exists { param =>
equals(param.name, name) && param.typeSignature == `type`
}
}
// finding method entity.copy(id: Option[Int])
currentType.members.find { symbol =>
symbol.isMethod && {
implicit val method = symbol.asMethod
hasName("copy") && hasReturnType(currentType) && hasParameter("id", typeOf[Option[Int]])
}
} match {
case Some(symbol) => {
val method = symbol.asMethod
val param = reify((
c.Expr[String](Literal(Constant("id"))).splice,
id.splice)).tree
c.Expr(
Apply(
Select(
reify(entity.splice).tree,
newTermName("copy")),
List( /*id.tree*/ )))
}
case None => c.abort(c.enclosingPosition, currentType + " needs method 'copy(..., id: Option[Int], ...): " + currentType + "'")
}
}
}
적용의 마지막 인수는 (위의 코드 블록의 하단 참조) 매개 변수의 목록입니다 (여기 : 메소드의 매개 변수를 '복사'). 어떻게 유형 c.Expr의 지정된 ID [옵션은 지능] [] 새로운 매크로 API의 도움으로 복사 방법에 이름을 매개 변수로 전달 될 수 있는가?
특히 다음 매크로 표현에서
c.Expr(
Apply(
Select(
reify(entity.splice).tree,
newTermName("copy")),
List(/*?id?*/)))
초래한다
entity.copy(id = id)
그래서 다음은 보유하고 있음
case class Test(s: String, id: Option[Int] = None)
// has to be compiled by its own
object Test extends App {
assert( Entity.withId(Test("scala rulz"), Some(1)) == Test("scala rulz", Some(1)))
}
누락 된 부분이 틀 / *? ID로 표시된다? * /.
해결법
-
==============================
1.여기에 또한 좀 더 범용의 구현입니다 :
여기에 또한 좀 더 범용의 구현입니다 :
import scala.language.experimental.macros object WithIdExample { import scala.reflect.macros.Context def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I] def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)( entity: c.Expr[T], id: c.Expr[I] ): c.Expr[T] = { import c.universe._ val tree = reify(entity.splice).tree val copy = entity.actualType.member(newTermName("copy")) val params = copy match { case s: MethodSymbol if (s.paramss.nonEmpty) => s.paramss.head case _ => c.abort(c.enclosingPosition, "No eligible copy method!") } c.Expr[T](Apply( Select(tree, copy), params.map { case p if p.name.decoded == "id" => reify(id.splice).tree case p => Select(tree, p.name) } )) } }
그것은 멤버라는 ID를 가진 어떤 경우 클래스에서 작동합니다, 더 그 형태가 무엇인지는 중요하지 않습니다 :
scala> case class Bar(arg0: String, id: Option[Int]) defined class Bar scala> case class Foo(x: Double, y: String, id: Int) defined class Foo scala> WithIdExample.withId(Bar("bar", None), Some(2)) res0: Bar = Bar(bar,Some(2)) scala> WithIdExample.withId(Foo(0.0, "foo", 1), 2) res1: Foo = Foo(0.0,foo,2)
케이스 클래스는 ID의 멤버가없는 경우, withId는 컴파일이됩니다 단지 아무것도하지 않습니다. 당신이 경우 컴파일 오류를 원하는 경우에, 당신은 사본에 경기에 추가 조건을 추가 할 수 있습니다.
편집 :로 유진 Burmako 그냥 트위터에 지적, 당신은 마지막에 AssignOrNamedArg를 사용하여 더 자연스럽게 작은 이것을 쓸 수 있습니다 :
c.Expr[T](Apply( Select(tree, copy), AssignOrNamedArg(Ident("id"), reify(id.splice).tree) :: Nil ))
케이스 클래스는 ID 회원이없는 경우이 버전은 컴파일되지 않습니다,하지만 가능성이 어쨌든 원하는 동작 할 수있다.
-
==============================
2.이것은 모든 부품이 조립되어 트래비스의 솔루션입니다 :
이것은 모든 부품이 조립되어 트래비스의 솔루션입니다 :
import scala.language.experimental.macros object WithIdExample { import scala.reflect.macros.Context def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I] def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)( entity: c.Expr[T], id: c.Expr[I] ): c.Expr[T] = { import c.universe._ val tree = reify(entity.splice).tree val copy = entity.actualType.member(newTermName("copy")) copy match { case s: MethodSymbol if (s.paramss.flatten.map(_.name).contains( newTermName("id") )) => c.Expr[T]( Apply( Select(tree, copy), AssignOrNamedArg(Ident("id"), reify(id.splice).tree) :: Nil)) case _ => c.abort(c.enclosingPosition, "No eligible copy method!") } } }
from https://stackoverflow.com/questions/13446528/howto-model-named-parameters-in-method-invocations-with-scala-macros by cc-by-sa and MIT license
'SCALA' 카테고리의 다른 글
[SCALA] 어떻게 플레이에 업로드 된 파일을 제공합니다! 2 스칼라를 사용하고 계십니까? (0) | 2019.11.16 |
---|---|
[SCALA] 우리가 인수를 필요로하지 않는 함수를 정의 할 괄호를 사용할 수 없습니다 경우 왜 스칼라에 빈 괄호를 사용 하는가? (0) | 2019.11.16 |
[SCALA] 어떻게 스칼라에서 피타고라스 정리를 작성합니까? (0) | 2019.11.16 |
[SCALA] 스칼라에서리스트에 요소를 추가합니다 (0) | 2019.11.16 |
[SCALA] 스칼라 : foldLeft 대 배 (0) | 2019.11.16 |