รับประเภทโครงสร้างด้วยวิธีการเรียนที่ไม่ระบุชื่อจากแมโคร


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> {class anon สุดท้าย {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398 ขอบคุณสำหรับรายงาน! ฉันจะดูสัปดาห์นี้
ยูจีน Burmako

1
หมายเหตุที่ผมเคยยื่นปัญหาที่นี่
เทรวิสบราวน์

ไม่ไม่ใช่ตัวบล็อกเกอร์ขอบคุณ - เคล็ดลับการเรียนแบบไม่ระบุชื่อพิเศษนั้นใช้ได้กับฉันทุกครั้งที่ฉันต้องการมัน ฉันเพิ่งสังเกตเห็น upvotes สองสามคำถามและอยากรู้เกี่ยวกับสถานะ
เทรวิสบราวน์

3
ส่วนสมาชิกประเภทไหนง่ายมาก -> wTF? คุณกำลังแตกอย่างมากในทางที่ดีแน่นอน :)
ZaoTaoBao

3
มีผู้โหวตเพิ่มขึ้น153 คนที่นี่และเพียง 1 คนสำหรับปัญหาใน scala-lang.org มีผู้โหวตมากขึ้นอาจได้รับการแก้ไขเร็วขึ้น?
moodboom

คำตอบ:


9

คำถามนี้เป็นคำถามที่ตอบในที่ซ้ำกันโดยเทรวิสที่นี่ มีลิงก์ไปยังปัญหาในตัวติดตามและการสนทนาของ Eugene (ในความคิดเห็นและรายชื่อผู้รับจดหมาย)

ในส่วน "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 ฉันคิดว่าคุณมีเครื่องมืออื่นใน Bat Belt ของคุณเช่นกัน ขอบคุณสำหรับหัวขึ้น: ผมถือว่า AST ของคุณคือ "การจัดฟันเสริมเก่าเคล็ดลับ" แต่ฉันเห็นว่าตอนนี้ ClassDef / new $anon {}สมัครที่ไม่ได้ห่อในบล็อกของตัวเองเช่นเดียวกับที่เกิดขึ้นกับ สิ่งที่ซื้อกลับบ้านของฉันคือในอนาคตฉันจะไม่ใช้anonในมาโครที่มี quasiquotes หรือชื่อพิเศษที่คล้ายกัน
som-snytt

q "$ {s: String}" ไวยากรณ์ล่าช้าเล็กน้อยโดยเฉพาะถ้าคุณใช้สรวงสวรรค์ เหมือนเดือนหน้ามากกว่าสัปดาห์หน้า
Denys Shabalin

@ som-snytt @ denys-shabalin มีกลอุบายพิเศษสำหรับประเภทโครงสร้าง a-la shapeless.Genericหรือไม่? ทั้งๆที่มีความตั้งใจที่ดีที่สุดของฉันในการบังคับAuxรูปแบบการคืนค่าคอมไพเลอร์ปฏิเสธที่จะมองผ่านประเภทโครงสร้าง
flavian
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.