Lấy một kiểu cấu trúc với các phương thức của lớp ẩn danh từ một macro


181

Giả sử chúng ta muốn viết một macro xác định một lớp ẩn danh với một số thành viên hoặc phương thức loại, và sau đó tạo một thể hiện của lớp đó được gõ tĩnh như một kiểu cấu trúc với các phương thức đó, v.v. Điều này có thể với hệ thống macro trong 2.10. 0, và phần thành viên loại cực kỳ dễ dàng:

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

(Đâu ReflectionUtilslà một đặc điểm tiện lợi cung cấp constructorphương pháp của tôi .)

Macro này cho phép chúng tôi chỉ định tên của thành viên loại của lớp ẩn danh dưới dạng chuỗi ký tự:

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

Lưu ý rằng nó được gõ một cách thích hợp. Chúng tôi có thể xác nhận rằng mọi thứ đang hoạt động như mong đợi:

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

Bây giờ giả sử rằng chúng ta cố gắng làm điều tương tự với một phương thức:

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

Nhưng khi chúng tôi dùng thử, chúng tôi không nhận được loại cấu trúc:

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

Nhưng nếu chúng ta gắn thêm một lớp ẩn danh ở đó:

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

Nó hoạt động:

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

scala> res0.test
res1: Int = 42

Điều này cực kỳ tiện lợi, nó cho phép bạn làm những việc như thế này , ví dụ như nhưng tôi không hiểu tại sao nó hoạt động và phiên bản thành viên loại hoạt động, nhưng không bar. Tôi biết điều này có thể không được xác định hành vi , nhưng nó có ý nghĩa gì không? Có cách nào sạch hơn để có được một kiểu cấu trúc (với các phương thức trên nó) từ một macro không?


14
Thật thú vị, nếu bạn viết cùng một mã trong REPL thay vì tạo nó trong một macro, nó hoạt động: scala> {lớp cuối cùng anon {def x = 2}; anon mới} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Cảm ơn báo cáo! Tôi sẽ xem xét trong tuần này.
Eugene Burmako

1
Lưu ý rằng tôi đã gửi một vấn đề ở đây .
Travis Brown

Không, không phải là một trình chặn, cảm ơn, thủ thuật lớp ẩn danh bổ sung đã làm việc cho tôi bất cứ khi nào tôi cần. Tôi chỉ nhận thấy một vài câu hỏi về câu hỏi và tò mò về tình trạng này.
Travis Brown

3
loại thành viên là cực kỳ dễ dàng -> wTF? bạn cực kỳ bẻ khóa! theo cách tốt dĩ nhiên :)
ZaoTao Bảo

3
Có 153 upvote ở đây, và chỉ có 1 cho vấn đề trên scala-lang.org . Nhiều upvote có thể giải quyết nó nhanh hơn?
psychboom

Câu trả lời:


9

Câu hỏi này được trả lời trùng lặp bởi Travis ở đây . Có các liên kết đến vấn đề trong trình theo dõi và thảo luận của Eugene (trong danh sách nhận xét và gửi thư).

Trong phần "Skylla và Charybdis" nổi tiếng của trình kiểm tra loại, anh hùng của chúng ta quyết định cái gì sẽ thoát khỏi sự ẩn danh đen tối và xem ánh sáng là một thành viên của loại cấu trúc.

Có một số cách để lừa người kiểm tra loại (không đòi hỏi mưu đồ của Odysseus là ôm một con cừu). Đơn giản nhất là chèn một câu lệnh giả để khối không trông giống như một lớp ẩn danh theo sau là sự khởi tạo của nó.

Nếu ông trùm thông báo rằng bạn là một thuật ngữ công khai không được tham chiếu từ bên ngoài, nó sẽ khiến bạn riêng tư.

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
Tôi sẽ chỉ lưu ý rằng tôi thực sự cung cấp cách giải quyết đầu tiên trong chính câu hỏi này (nó chỉ chưa được phân loại ở đây). Tôi rất vui khi có câu trả lời này kết thúc câu hỏi tôi nghĩ rằng tôi đã mơ hồ chờ đợi lỗi được sửa.
Travis Brown

@TravisBrown Tôi cá là bạn cũng có các công cụ khác trong Bat Belt của mình. Thx cho những người đứng đầu: Tôi cho rằng AST của bạn là "thủ thuật niềng răng thêm cũ", nhưng tôi thấy bây giờ ClassDef / Áp dụng không được gói trong Khối riêng của họ, như đã xảy ra new $anon {}. Điều đáng nói khác của tôi là trong tương lai tôi sẽ không sử dụng các anonmacro với các quasiquote hoặc các tên đặc biệt tương tự.
som-snytt

Cú pháp "$ {s: String}" bị trì hoãn một chút, đặc biệt nếu bạn đang sử dụng thiên đường. Vì vậy, giống như tháng tới hơn là tuần tới.
Denys Shabalin

@ som-snytt @ denys-shabalin, có một loại mánh khóe đặc biệt nào cho các loại cấu trúc shapeless.Generickhông? Mặc dù ý định tốt nhất của tôi là buộc các Auxkiểu trả về mẫu, trình biên dịch từ chối xem qua kiểu cấu trúc.
flavian
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.