2013-01-17 35 views
180

假設我們要編寫一個宏,該宏定義了具有某些類型成員或方法的匿名類,然後創建該類的實例,該靜態類型爲類型爲結構類型那些方法等等,這是可能的,在2.10.0宏系統和類型成員的部分是極其容易的:(其中ReflectionUtilsconvenience trait提供我的constructor方法)從宏中獲取具有匿名類方法的結構類型

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

這個宏讓我們指定匿名類的類型成員的名字爲字符串文字:

scala> MacroExample.foo("T") 
res0: AnyRef{type T = Int} = [email protected] 

請注意,它是適當類型的。我們可以證實,一切都按預期工作:

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 = [email protected] 

但是,如果我們堅持一個額外的匿名類有:

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} = [email protected] 

scala> res0.test 
res1: Int = 42 

這是非常方便的,它可以讓你做的事情一樣this,例如 - 但我不明白爲什麼它的工作原理,以及該類型成員版本的作品,但不是bar。我知道這個may not be defined behavior,但它有什麼意義嗎?是否有更簡潔的方法從宏中獲取結構類型(以及其中的方法)?

+14

有趣的是,如果寫在REPL相同的代碼,而不是在一個宏產生它的,它的工作原理: 階> {最終類匿名{DEF X = 2}; new anon} res1:AnyRef {def x:Int} = anon $ 1 @ 5295c398。感謝報告!我會在這個星期看看。 –

+1

請注意,我已在[此處]提交了一個問題(https://issues.scala-lang.org/browse/SI-6992)。 –

+0

不是,不是一個封鎖,謝謝 - 額外的匿名類技巧在我需要的時候爲我工作。我剛纔注意到了一些關於這個問題的提議,並對這個地位感到好奇。 –

回答

8

這個問題一式兩份回答Travis here。在跟蹤器和Eugene的討論中(在評論和郵件列表中)有問題的鏈接。

我們的英雄在着名的「Skylla and Charybdis」部分的類型檢查器中決定什麼應該避開黑暗的匿名並將光看作是結構類型的一員。

有幾種方法可以欺騙類型檢查器(這並不需要奧德賽斯抱抱羊的策略)。最簡單的方法是插入一個僞語句,以使塊看起來不像是一個匿名類,然後是它的實例。

如果typer注意到你是一個公共術語,而這個公共術語沒有被外部引用,它會讓你變得私人。

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

我只會注意到,我實際上在這個問題本身中提供了第一個解決方法(這裏只是未準備分類)。我很高興有這個答案能夠解決這個問題 - 我想我隱約地等待bug修復。 –

+0

@TravisBrown我敢打賭,你的蝙蝠帶上還有其他工具。 Thx的頭腦:我認爲你的AST是「舊的額外大括號技巧」,但我現在看到ClassDef/Apply沒有被包裝在自己的Block中,就像'new $ anon {}'所發生的一樣。我的另外一件事是,將來我不會在宏與quasiquotes或類似的特殊名稱中使用'anon'。 –

+0

q「$ {s:String}」語法會稍微延遲一點,特別是如果您正在使用天堂。所以更像下個月而不是下週。 –