매크로에서 익명 클래스의 메소드를 사용하여 구조 유형 가져 오기


181

어떤 타입 멤버 나 메소드로 익명 클래스를 정의한 다음, 그 메소드 등으로 구조적 타입으로 정적으로 타입이 지정된 클래스의 인스턴스를 생성하는 매크로를 작성하려고한다고 가정 해 봅시다. 이것은 2.10의 매크로 시스템에서 가능합니다. 0이며 형식 멤버 부분은 매우 쉽습니다.

object MacroExample extends ReflectionUtils {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  def foo(name: String): Any = macro foo_impl
  def foo_impl(c: Context)(name: c.Expr[String]) = {
    import c.universe._

    val Literal(Constant(lit: String)) = name.tree
    val anon = newTypeName(c.fresh)

    c.Expr(Block(
      ClassDef(
        Modifiers(Flag.FINAL), anon, Nil, Template(
          Nil, emptyValDef, List(
            constructor(c.universe),
            TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
          )
        )
      ),
      Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
    ))
  }
}

( 나의 방법 을 제공 ReflectionUtils하는 편의 특성 은 어디에 있습니까 constructor?)

이 매크로를 사용하면 익명 클래스의 형식 멤버 이름을 문자열 리터럴로 지정할 수 있습니다.

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6

적절하게 입력되었습니다. 모든 것이 예상대로 작동하는지 확인할 수 있습니다.

scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>

이제 우리는 방법으로 같은 일을하려고한다고 가정하자 :

def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
  import c.universe._

  val Literal(Constant(lit: String)) = name.tree
  val anon = newTypeName(c.fresh)

  c.Expr(Block(
    ClassDef(
      Modifiers(Flag.FINAL), anon, Nil, Template(
        Nil, emptyValDef, List(
          constructor(c.universe),
          DefDef(
            Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
            c.literal(42).tree
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  ))
}

그러나 시도해 볼 때 구조적 유형을 얻지 못합니다.

scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492

그러나 거기에 익명 클래스를 추가하면

def baz(name: String): Any = macro baz_impl
def baz_impl(c: Context)(name: c.Expr[String]) = {
  import c.universe._

  val Literal(Constant(lit: String)) = name.tree
  val anon = newTypeName(c.fresh)
  val wrapper = newTypeName(c.fresh)

  c.Expr(Block(
    ClassDef(
      Modifiers(), anon, Nil, Template(
        Nil, emptyValDef, List(
          constructor(c.universe),
          DefDef(
            Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
            c.literal(42).tree
          )
        )
      )
    ),
    ClassDef(
      Modifiers(Flag.FINAL), wrapper, Nil,
      Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
    ),
    Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
  ))
}

효과가있다:

scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

이것은 매우 편리합니다 - 당신이 같은 일을 할 수있다 , 예를-하지만이 작동하고, 형식 멤버 버전의 작품,하지만 난 왜 이해가 안 돼요 bar. 나는 이것이 행동으로 정의되지는 않았지만 어떤 의미가 있는지 알고 있습니다 . 매크로에서 구조 유형을 얻는 더 깨끗한 방법이 있습니까?


14
흥미롭게도 REPL에서 매크로로 생성하는 대신 동일한 코드를 작성하면 다음과 같이 작동합니다. scala> {final class anon {def x = 2}; 새로운 anon} res1 : AnyRef {def x : Int} = anon $ 1 @ 5295c398. 보고서 주셔서 감사합니다! 이번 주에 살펴 보겠습니다.
유진 버마 코

1
여기 에 문제를 제기했습니다 .
Travis Brown

블로커가 아닌 아닙니다. 감사합니다. 익명 클래스 트릭이 필요할 때마다 효과가있었습니다. 방금 질문에 대한 몇 가지 공감대를 보았고 상태에 대해 궁금했습니다.
트래비스 브라운

3
유형 멤버 부분은 매우 쉽습니다-> wTF? 좋은 방법으로 :)
ZaoTaoBao

3
여기에 153 개의 공감대가 있으며 scala-lang.org문제에 대해서는 1 개만 있습니다. 더 많은 투표가 더 빨리 해결 될 수 있습니까?
moodboom

답변:


9

이 질문은 여기 Travis에 의해 두 번 답변 됩니다 . 트래커와 유진의 토론 (댓글 및 메일 링리스트)에 문제에 대한 링크가 있습니다.

유형 검사기의 유명한 "Skylla and Charybdis"섹션에서, 우리의 영웅은 어두운 익명 성을 피하고 구조적 유형의 구성원으로 빛을 볼 것을 결정합니다.

타입 체커를 속이는 몇 가지 방법이 있습니다 (Odysseus의 양 포옹 계획은 포함되지 않습니다). 가장 간단한 방법은 블록이 익명 클래스처럼 보이지 않고 그 뒤에 인스턴스화가 발생하도록 더미 명령문을 삽입하는 것입니다.

입력자가 외부에서 참조하지 않는 공개 용어임을 알게되면 귀하를 비공개로 만들 것입니다.

object Mac {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  /* Make an instance of a structural type with the named member. */
  def bar(name: String): Any = macro bar_impl

  def bar_impl(c: Context)(name: c.Expr[String]) = {
    import c.universe._
    val anon = TypeName(c.freshName)
    // next week, val q"${s: String}" = name.tree
    val Literal(Constant(s: String)) = name.tree
    val A    = TermName(s)
    val dmmy = TermName(c.freshName)
    val tree = q"""
      class $anon {
        def $A(i: Int): Int = 2 * i
      }
      val $dmmy = 0
      new $anon
    """
      // other ploys
      //(new $anon).asInstanceOf[{ def $A(i: Int): Int }]
      // reference the member
      //val res = new $anon
      //val $dmmy = res.$A _
      //res
      // the canonical ploy
      //new $anon { }  // braces required
    c.Expr(tree)
  }
}

2
나는 실제로이 질문 자체의 첫 번째 해결 방법을 제공한다는 사실에 주목 할 것입니다 (여기서는 인용 부호가 없습니다). 이 답변으로 문제를 해결하게되어 기쁩니다. 버그가 수정 될 때까지 모호하게 기다리고있었습니다.
트래비스 브라운

@TravisBrown 나는 당신이 당신의 박쥐 벨트에 다른 도구를 가지고 내기. 헤딩을위한 Thx : 나는 AST가 "오래된 여분의 괄호 트릭"이라고 생각했지만 ClassDef / Apply는에서와 같이 자체 블록에 싸여 있지 않습니다 new $anon {}. 다른 테이크 아웃은 미래 anon에 준 따옴표 또는 유사한 특수 이름을 가진 매크로에서 사용하지 않을 것 입니다.
som-snytt

q "$ {s : String}"구문, 특히 파라다이스를 사용하는 경우 구문이 약간 지연됩니다. 다음 주가 아니라 다음 달이 더 좋아요.
거부 거부 Shabalin

@ som-snytt @ denys-shabalin, 구조 유형 a-la에 대한 특별한 종류의 속임수가 shapeless.Generic있습니까? Aux패턴 리턴 유형 을 강제하려는 가장 좋은 의도에도 불구하고 컴파일러는 구조적 유형을 통해 보려고 거부합니다.
flavian
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.