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は空っぽのようです。蓄積された木のひもを保持することを期待していました。

マクロコンパイルステージから次のステージにデータを渡して、実行時に表示されるようにするにはどうすればよいですか?もちろん、現在の文字列を次のメソッドに明示的に渡すこともできますが、それは避けたいと思います。

4

3 に答える 3

3

基本的に、次のような抽象化が必要ですQueryable。1)コレクションAPI(、、など)を提供しfromselect2)呼び出しを具体化して内部に蓄積することにより、呼び出されたメソッドを記憶します。

この概念は、ScalaDaysスライド[1]である程度説明されており、Slick(オープンソース)[2]で実装されています。ちなみに、LINQでは、呼び出しを具体化して、たとえば[3]で説明されているように、Queryableそれらを実装するオブジェクトにフィードするメソッドとほぼ同じです。IQueryable

リンク:

  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
于 2012-08-02T15:06:03.327 に答える
2

問題は、あるマクロ呼び出しから次のマクロ呼び出しに情報を渡さないことです。これらはすべてコンパイル時に発生するため、機能するはずです。問題は、lastと呼ばれるマクロにあります。を返すためc.universe.reify(new Where(result.toString))new Where(result.toString)実行時に呼び出されます。そして、result空になります。できることはreturnですc.Expr(tree)。ここで、のコンストラクターをを含むリテラルにtree適用します。WhereStringresult.toString

また、コードはマクロ呼び出しがコンパイルされる順序に依存することに注意してください。複数のコードファイルでこれらのマクロを複数回呼び出す場合はresult、以前の呼び出しからの情報が含まれている可能性があります。アプローチ全体を再考するのがおそらく最善でしょう。

于 2012-08-02T13:01:48.170 に答える
0

@Kimが指摘しているように、情報の集約は問題ではありませんが、マクロ展開によりresult.toString、実行時に実際に空の場合に評価されるコードが生成されます。私はあなたと同じような問題を抱えていてresult.toStringresultExpr(c).splice

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

(@Kimも指摘しているように、これはすべてのマクロ呼び出しの累積結果をランタイムにフィードバックするので注意してください!)

于 2012-08-23T10:21:45.103 に答える