我正在尝试构建一些类似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
似乎是空的。我预计它会保留树木的累积字符串。
如何将数据从宏编译阶段传递到下一阶段,以便在运行时显示? 我当然可以明确地将当前字符串传递给下一个方法,但我想避免这种情况。
答案 0 :(得分:3)
基本上你需要有一个Queryable
抽象:1)提供集合API(from
,select
等),2)记住通过它调用的方法将呼叫召唤并将其累积在内部。
这个概念在我们的ScalaDays幻灯片[1]中有所解释,并在Slick(开源)[2]中实现。顺便说一下,在LINQ中,它们与Queryable
上的方法大致相同,这些方法将调用并将它们提供给实现IQueryable
的对象,例如{{1}}。如[3]中所述。
链接:
答案 1 :(得分:2)
问题不在于将信息从一个宏调用传递到下一个宏调用。所有这些都发生在编译时,所以这应该工作。问题在于最后调用的宏。由于它返回c.universe.reify(new Where(result.toString))
,因此在运行时调用new Where(result.toString)
。然后result
将为空。您可以做的是返回c.Expr(tree)
,其中tree
将Where
的构造函数应用于包含String
的{{1}}文字。
此外,您应该注意您的代码取决于编译宏调用的顺序。如果您在多个代码文件中多次调用这些宏,result.toString
可能包含先前调用的信息。最好重新考虑整个方法。
答案 2 :(得分:0)
由于@Kim指出聚合信息不是问题,但宏扩展将生成在运行时确实为空时评估result.toString
的代码。我遇到了类似的问题,最后用result.toString
resultExpr(c).splice
private def resultExpr(c :Context) = {
import c.universe._
c.Expr[String](Literal(Constant(result.toString)))
}
(因为@Kim还指出这会将所有宏调用的累积结果反馈给运行时,所以要小心!)