Mendapatkan tipe struktural dengan metode kelas anonim dari makro

181

Misalkan kita ingin menulis makro yang mendefinisikan kelas anonim dengan beberapa tipe anggota atau metode, dan kemudian membuat contoh kelas yang secara statis diketik sebagai tipe struktural dengan metode-metode itu, dll. Ini dimungkinkan dengan sistem makro di 2.10. 0, dan bagian tipe anggota sangat mudah:

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

(Dimana ReflectionUtilsadalah sifat kenyamanan yang menyediakan saya constructormetode.)

Makro ini memungkinkan kita menentukan nama anggota tipe kelas anonim sebagai string literal:

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

Perhatikan bahwa itu diketik dengan tepat. Kami dapat mengonfirmasi bahwa semuanya berfungsi seperti yang diharapkan:

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

Sekarang anggaplah kita mencoba melakukan hal yang sama dengan metode:

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

Tetapi ketika kami mencobanya, kami tidak mendapatkan tipe struktural:

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

Tetapi jika kita memiliki kelas anonim tambahan di sana:

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

Berhasil:

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

scala> res0.test
res1: Int = 42

Ini sangat berguna — ini memungkinkan Anda melakukan hal-hal seperti ini , misalnya — tetapi saya tidak mengerti mengapa ini bekerja, dan versi tipe anggota berfungsi, tetapi tidak bar. Saya tahu ini mungkin bukan perilaku yang didefinisikan , tetapi apakah itu masuk akal? Apakah ada cara yang lebih bersih untuk mendapatkan tipe struktural (dengan metode di atasnya) dari makro?

Travis Brown
sumber
14
Cukup menarik, jika Anda menulis kode yang sama dalam REPL alih-alih menghasilkannya dalam makro, ia bekerja: scala> {kelas akhir anon {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Terima kasih atas laporannya! Saya akan memeriksanya minggu ini.
Eugene Burmako
1
Perhatikan bahwa saya telah mengajukan masalah di sini .
Travis Brown
Tidak, bukan pemblokir, terima kasih - trik kelas anonim ekstra telah berhasil untuk saya setiap kali saya membutuhkannya. Saya hanya melihat beberapa suara positif pada pertanyaan dan ingin tahu tentang statusnya.
Travis Brown
3
ketik anggota bagian sangat mudah -> wTF? Anda benar-benar retak! tentu saja dengan cara yang baik :)
ZaoTaoBao
3
Ada 153 suara positif di sini, dan hanya 1 untuk masalah ini di scala-lang.org . Lebih banyak gangguan mungkin bisa diselesaikan lebih cepat?
moodboom

Jawaban:

9

Pertanyaan ini dijawab dalam rangkap dua oleh Travis di sini . Ada tautan ke masalah ini di pelacak dan ke diskusi Eugene (dalam komentar dan milis).

Di bagian "Skylla dan Charybdis" yang terkenal dari pemeriksa tipe, pahlawan kita memutuskan apa yang akan lepas dari anonimitas gelap dan melihat cahaya sebagai anggota tipe struktural.

Ada beberapa cara untuk mengelabui pemeriksa tipe (yang tidak mengharuskan si Odysseus memeluk seekor domba). Yang paling sederhana adalah memasukkan pernyataan dummy sehingga blok tidak terlihat seperti kelas anonim diikuti oleh instantiasinya.

Jika pemberitahuan lebih lanjut bahwa Anda adalah istilah publik yang tidak dirujuk oleh pihak luar, itu akan membuat Anda pribadi.

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)
  }
}
som-snytt
sumber
2
Saya hanya akan mencatat bahwa saya benar-benar memberikan solusi pertama dalam pertanyaan ini sendiri (itu hanya kuasiquote di sini). Saya senang jawaban ini menyelesaikan pertanyaan - saya pikir saya sudah samar-samar menunggu bug diperbaiki.
Travis Brown
@ TravisBrown Saya yakin Anda memiliki alat lain di Bat Belt Anda juga. Terima kasih banyak: Saya berasumsi AST Anda adalah "trik kawat gigi ekstra lama", tetapi sekarang saya melihat bahwa ClassDef / Terapkan tidak dibungkus dalam Blok mereka sendiri, seperti yang terjadi pada new $anon {}. anonTujuan saya yang lain adalah di masa depan saya tidak akan menggunakan makro dengan kuasiquote, atau nama-nama khusus serupa.
som-snytt
q "$ {s: String}" sintaks sedikit ditunda, terutama jika Anda menggunakan firdaus. Jadi lebih seperti bulan depan daripada minggu depan.
Denys Shabalin
@ som-snytt @ denys-shabalin, apakah ada tipu daya khusus untuk tipe struktural a-la shapeless.Generic? Terlepas dari niat terbaik saya untuk memaksa Auxtipe pola pengembalian kompiler menolak untuk melihat melalui tipe struktural.
Flavia