【问题标题】:What's the recommended way to pass the results of macro computations to run-time?将宏计算结果传递给运行时的推荐方法是什么?
【发布时间】:2012-08-02 12:19:37
【问题描述】:

我正在尝试构建一些类似 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 似乎是空的。我希望它能够容纳堆积的树木。

如何将我的数据从宏编译阶段传递到下一阶段,以便在运行时显示? 我当然可以将当前字符串显式传递给下一个方法,但我想避免这种情况。

【问题讨论】:

    标签: sql scala macros compilation


    【解决方案1】:

    基本上你需要有一个 Queryable 抽象:1) 提供集合 API(fromselect 等),2) 通过具体化调用并累积它们来记住在其上调用的方法里面。

    这个概念在我们的 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

    【讨论】:

    • 嗨,尤金,谢谢,我去看看。看起来有一个真正的解决方案。 :-)
    【解决方案2】:

    问题在于没有将信息从一个宏调用传递到下一个宏调用。所有这些都发生在编译时,所以应该可以。问题在于最后调用的宏。因为它返回c.universe.reify(new Where(result.toString)),所以new Where(result.toString) 在运行时被调用。然后result 将为空。你可以做的是返回c.Expr(tree),其中treeWhere 的构造函数应用于包含result.toStringString 文字。

    另外,您应该注意,您的代码取决于宏调用的编译顺序。如果您在多个代码文件中多次调用这些宏,result 可能包含来自先前调用的信息。最好重新考虑您的整个方法。

    【讨论】:

    • 是的,它只是一些原型来解决我想在编译时打印树的事实,但我无法让 Eclipse 向我显示编译时输出。 :-) 你有更好的解决方案的想法吗?
    • 你的意思是这样的? stackoverflow.com/questions/11677609/…
    【解决方案3】:

    正如@Kim 指出的那样,聚合信息不是问题,而是宏扩展将生成在运行时评估result.toString 的代码,而result.toString 确实为空。我遇到了和你类似的问题,最后用resultExpr(c).splice替换result.toString

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

    (正如@Kim 还指出的那样,这会将所有宏调用的累积结果反馈给运行时,所以要小心!)

    【讨论】:

      猜你喜欢
      • 2021-01-26
      • 1970-01-01
      • 2017-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-21
      • 2012-10-29
      • 1970-01-01
      相关资源
      最近更新 更多