2012-08-02 13 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抽象化を持っている必要があります。

この概念は幾分我々のScalaDaysスライドで説明されている[1]とスリックに実装され(オープンソースである)[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

こんにちはユージーン、ありがとう、私は見ていきます。 1つの真の解決策があるように見えます。 :-) – soc

2

問題は、次の1つのマクロ呼び出しからの情報を渡していません。それらはすべてコンパイル時に発生するので、うまくいくはずです。問題は、最後に呼ばれるマクロにあります。 c.universe.reify(new Where(result.toString))を返すので、実行時にnew Where(result.toString)が呼び出されます。そしてresultは空になります。あなたにできることはtreeresult.toStringが含まれてStringリテラルへWhereのコンストラクタを適用c.Expr(tree)を返すです。

また、あなたはあなたのコードがマクロ呼び出しがコンパイルされる順序に依存することに注意してください。これらのマクロを複数のコードファイルで複数コールした場合、resultには以前の呼び出しの情報が含まれている可能性があります。おそらくあなたの全体のアプローチを考え直すのが最善でしょう。 1)、コレクションAPI(fromselect、など)を提供2)呼び出しをreifyingと内部それらを蓄積することにより、その上で呼び出されたメソッドを記憶しています:

+0

はい、コンパイル時にツリーを印刷したいという事実を回避するためのプロトタイプでしたが、Eclipseにコンパイル時の出力を表示させることができませんでした。あなたはもっと良い解決策を考えていますか? – soc

+0

このような意味ですか? http://stackoverflow.com/questions/11677609/how-do-i-print-an-expanded-macro-in-scala –

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も指摘するように、これは戻ってランタイムにすべてのマクロ呼び出しの累積結果を供給するので、注意します!)

関連する問題