Bir makrodan anonim sınıfın yöntemleriyle yapısal bir tür elde etme


181

Bazı tür üyeler veya yöntemlerle anonim bir sınıf tanımlayan ve daha sonra bu yöntemlerle vb. Statik olarak yapısal tür olarak yazılan bir sınıf örneği oluşturan bir makro yazmak istediğimizi varsayalım. Bu, 2.10'daki makro sistemiyle mümkündür. 0 ve tür üye kısmı son derece kolaydır:

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

(Nerede ReflectionUtilsbir olan kolaylık özellik benim sağlayan constructoryöntem.)

Bu makro, anonim sınıfın tür üyesi adını dize hazır bilgisi olarak belirtmemizi sağlar:

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

Uygun şekilde yazıldığını unutmayın. Her şeyin beklendiği gibi çalıştığını doğrulayabiliriz:

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

Şimdi, aynı şeyi bir yöntemle yapmaya çalıştığımızı varsayalım:

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

Ancak denediğimizde yapısal bir tür elde etmiyoruz:

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

Ama orada fazladan anonim bir sınıf yapıştırırsak:

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

İşe yarıyor:

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

scala> res0.test
res1: Int = 42

Bu son derece kullanışlı-it gibi şeyler yapalım olan bu için, örneğin-ama işe yarar ve tipi üyesi versiyonu çalışır, ancak neden anlamıyorum bar. Bunun tanımlanmış bir davranış olmayabileceğini biliyorum , ama bir anlamı var mı? Bir makrodan yapısal bir tür (üzerindeki yöntemlerle) almanın daha temiz bir yolu var mı?


14
İlginçtir ki, aynı kodu bir makroda oluşturmak yerine REPL'e yazarsanız çalışır: scala> {final class anon {def x = 2}; yeni anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Rapor için teşekkürler! Bu hafta bir göz atacağım.
Eugene Burmako

1
Burada bir sorun yaşadığımı unutmayın .
Travis Brown

Hayır, bir engelleyici değil, teşekkürler - ekstra anonim sınıf numarası, ihtiyaç duyduğumda benim için çalıştı. Soruyla ilgili bir kaç upvotes fark ettim ve durumu merak ettim.
Travis Brown

3
tipi üye bölümü son derece kolaydır -> wTF? tabii ki iyi bir şekilde son derece çatlak! :)
ZaoTaoBao

3
Burada 153 upvotes var ve scala-lang.org'daki konu için sadece 1 tane var . Daha fazla oy daha hızlı çözülebilir?
moodboom

Yanıtlar:


9

Bu soru burada Travis tarafından iki kez cevaplanmıştır . İzleyicide konuya ve Eugene'nin tartışmasına bağlantılar vardır (yorumlar ve posta listesinde).

Tip denetleyicisinin ünlü "Skylla ve Charybdis" bölümünde, kahramanımız neyin karanlık anonimlikten kaçacağına karar verir ve ışığı yapısal türün bir üyesi olarak görür.

Tip denetleyiciyi kandırmanın birkaç yolu vardır (Odysseus'un bir koyun sarılmayı planlaması gerekmez). En basit olanı, bloğun anonim bir sınıfa benzememesi ve bunu takiben kukla bir ifade eklemektir.

Typer, dışarıda referans verilmeyen genel bir terim olduğunuzu fark ederse, sizi özel yapar.

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
Sadece bu sorunun kendisinde ilk geçici çözümü sağladığımı not edeceğim (sadece burada belirtilmemiş). Bu cevabın soruyu tamamlamasından mutluluk duyuyorum - sanırım hatanın düzeltilmesini bekliyorum.
Travis Brown

@TravisBrown Bahse girerim Yarasa Kemerinizde başka aletler de var. Heads for thx: AST'nizin "eski ekstra parantez hilesi" olduğunu varsaydım, ancak şimdi ClassDef / Apply'ın olduğu gibi kendi Bloklarına sarılmadığını görüyorum new $anon {}. Diğer anongötürüşüm, gelecekte yarı tırnak veya benzer özel isimlerle makrolarda kullanmayacağım .
som-snytt

q "$ {s: String}" sözdizimi, özellikle cennet kullanıyorsanız biraz gecikir. Gelecek hafta yerine gelecek ay gibi.
Denys Shabalin

@ som-snytt @ denys-shabalin, a-la yapısal tipleri için özel bir hile var shapeless.Genericmı? AuxDesen döndürme türlerini zorlamak için en iyi niyetime rağmen , derleyici yapısal türden görmeyi reddediyor.
flavian
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.