2012-08-02 56 views
9

我正在嘗試構建一些類似SQL的抽象,並且遇到了問題。將宏計算結果傳遞給運行時的建議方法是什麼?

這是一個簡化的「數據庫表」:

trait Coffee { 
    def id: Long 
    def name: String 
    def brand: String 
} 

這是我的查詢抽象:

import language.experimental.macros 

object Query { 
    def from[T] = 
    macro QueryMacros.fromMacro[T] 
} 

class From[T] { 
    def select[S](s: T => S): Select[T] = 
    macro QueryMacros.selectMacro[T, S] 
} 

class Select[T] { 
    def where(pred: T => Boolean): Where = 
    macro QueryMacros.whereMacro[T] 
} 

class Where(val result: String) 

這是我的宏實現:

import scala.reflect.macros.Context 

object QueryMacros { 
    val result = new StringBuilder 

    def fromMacro[T : c.WeakTypeTag](c: Context): c.Expr[From[T]] = { 
    result ++= ("FROM " + c.weakTypeOf[T]) 
    c.universe.reify(new From[T]) 
    } 

    def selectMacro[T : c.WeakTypeTag, S : c.WeakTypeTag](c: Context)(s: c.Expr[T => S]): c.Expr[Select[T]] = { 
    result ++= ("SELECT " + s.tree) 
    c.universe.reify(new Select[T]) 
    } 

    def whereMacro[S](c: Context)(pred: c.Expr[S]): c.Expr[Where] = { 
    result ++= ("WHERE " + pred.tree) 
    c.universe.reify(new Where(result.toString)) 
    } 
} 

這是我的示例代碼:

object Main extends App { 
    println("Query start") 
    val query = 
    Query.from[Coffee] 
     .select(_.id) 
     .where(_.brand == "FairTrade") 

    println(query.result) 
    println("Query end") 
} 

它編譯並運行正常,但輸出是:

Query start 

Query end 

基本上,result似乎是空的。我預計它會保存積累的樹木串。

如何將數據從宏編譯階段傳遞到下一階段,以便在運行時顯示? 我當然可以將當前字符串顯式傳遞給下一個方法,但我想避免這種情況。

回答

3

基本上你需要有一個Queryable抽象:1)提供了集合API(fromselect等),2)通過回憶的reifying電話和內部積累他們被稱爲它的方法。

這個概念在我們的ScalaDays幻燈片[1]中有所解釋,並在Slick(它是開源的)[2]中實現。順便說一句,在LINQ中,它們與Queryable上的方法大致相同,這些方法將通話重新通知並將它們饋送到實現IQueryable的對象,例如,如[3]中所述。

鏈接:

  1. http://scalamacros.org/talks/2012-04-18-ScalaDays2012.pdf
  2. https://github.com/slick/slick/tree/master/src/main/scala/scala/slick/queryable
  3. http://community.bartdesmet.net/blogs/bart/archive/2007/04/06/the-iqueryable-tales-linq-to-ldap-part-1-key-concepts.aspx
+0

嗨尤金,謝謝,我會看看。看起來有一個真正的解決方案。 :-) – soc 2012-08-02 15:43:15

2

問題是沒有將信息從一個宏調用傳遞到下一個宏調用。所有這些都發生在編譯時,所以應該工作。問題在於最後調用的宏。由於它返回c.universe.reify(new Where(result.toString)),因此在運行時調用new Where(result.toString)。然後result將爲空。您可以做的是返回c.Expr(tree),其中treeWhere的構造函數應用於包含result.toStringString文字。

此外,您應該注意,您的代碼取決於編譯宏調用的順序。如果您在幾個代碼文件中有多個對這些宏的調用,result可能包含以前調用的信息。重新考慮你的整個方法可能是最好的。

+0

是的,它只是一些原型來解決我想在編譯時打印樹的事實,但我無法讓Eclipse向我顯示編譯時輸出。 :-)你有沒有更好的解決方案的想法? – soc 2012-08-02 14:07:36

+0

您的意思是? http://stackoverflow.com/questions/11677609/how-do-i-print-an-expanded-macro-in-scala – 2012-08-02 14:17:08

0

由於@Kim指出彙總的信息是沒有問題的,但是,宏觀擴張將產生評估result.toString代碼在運行時它確實是空的。我也有類似的問題,因爲你並結束了與resultExpr(c).splice

private def resultExpr(c :Context) = { 
    import c.universe._ 
    c.Expr[String](Literal(Constant(result.toString))) 
} 

做等價替換result.toString(如@Kim還指出,這將養活所有的宏調用回運行時的累積結果,所以要小心!)

相關問題