【问题标题】:Scala macro for shortcut用于快捷方式的 Scala 宏
【发布时间】:2014-02-25 16:14:50
【问题描述】:

我定义了以下宏来从当前位置获取文件、行和对象/类: http://pastebin.com/UsNLemnK

使用SBT,我定义了两个项目,为了先编译宏,然后是使用这些宏的实际项目。

这些宏的用途是用于日志方法:

def log( msg: Any, srcFile: String = "", srcLine: String = "", srcClass:String = "")

然后我使用这个日志方法如下:

log(msg, s"$F_",s"$L_",s"$C_")

其中 F_、L_ 和 C_ 在宏中定义。

现在,我想创建一个快捷方式来避免这个样板,然后调用:

log(msg)

应该自动替换为

log(msg, s"$F_",s"$L_",s"$C_")

我可以定义一个宏来做到这一点:

def log_(msg: String) : Unit = macro logImpl
def logImpl( c: Context )(msg: c.Expr[String]): c.Expr[Unit] = {
  import c.universe._
  reify( log(msg.splice, srcFile=s"$F_", srcLine=s"$L_", srcClass=s"$C_") )
}

但是再说一遍,这个宏需要在项目之前编译好,日志函数本身是在哪里定义的……所以我没看到怎么解决编译依赖循环……

关于如何做到这一点的任何建议? 谢谢

【问题讨论】:

  • 是什么阻止您将 log 函数移至宏项目?
  • 感觉不太“干净”,因为日志功能非常复杂......我必须将许多部分从我的核心项目移动到宏项目​​。我希望有更好的方法

标签: scala scala-2.10 scala-macros


【解决方案1】:

除非使用 macro annotations(这必然会显着改变您的 API 语法),否则您必须面对的问题是您需要日志函数的类型检查标识符。

由于您无法导入整个 log 实现,解决方案是:

  • 将方法包装成特征,
  • 在“宏”项目中定义此特征,
  • log_方法添加一个隐式参数,
  • 在您的“主”项目中,创建此 trait 的实现,并在您想使用 log_ 宏的任何地方都可见的implicit val 中实例化此实现(例如,在包对象中)。李>

当然,您也可以在此处使用简单的FunctionN 并避免 trait 定义和实现,但这样可以避免与其他同类型隐式的潜在冲突。

一般来说,您的代码如下所示:

//"macro" project
trait EncapsulatingTrait {
  def yourMethod(...)
}

object Macros {
  def myMacro(...)(implicit param: EncapsulatingTrait) = macro myMacroImpl
  def myMacroImpl( c: Context )(...)
                          (param: c.Expr[EncapsulatingTrait]): c.Expr[...] = {
    import c.universe._
    reify(param.splice.yourMethod(...))
  }
}

//--------------------------
//"main" project
class Impl extends EncapsulatingTrait {
  def yourMethod(...)
}

...

implicit val defaultParam = new Impl

import Macros.myMacro

myMacro(...)

在您的具体情况下,以下是实现的样子:

//"macro" project
package yourpackage

import java.io.File
import language.experimental.macros
import scala.reflect.macros.Context

trait LogFunction {
  def log( msg: Any, srcFile: String = "", srcLine: Int = -1, srcClass:String = "")
}


object Macros {
      // get current line in source code
      def L_ : Int = macro lineImpl
      def lineImpl( c: Context ): c.Expr[Int] = {
        import c.universe._
        val line = Literal( Constant( c.enclosingPosition.line ) )
        c.Expr[Int]( line )
      }

      // get current file from source code (relative path)
      def F_ : String = macro fileImpl
      def fileImpl( c: Context ): c.Expr[String] = {
        import c.universe._
        val absolute = c.enclosingPosition.source.file.file.toURI
        val base = new File( "." ).toURI
        val path = Literal( Constant( c.enclosingPosition.source.file.file.getName() ) )
        c.Expr[String]( path )
      }

      // get current class/object (a bit sketchy)
      def C_ : String = macro classImpl
      def classImpl( c: Context ): c.Expr[String] = {
        import c.universe._

        val class_ = Literal( Constant( c.enclosingClass.toString.split(" ")( 1 ) ) )  
        c.Expr[String]( class_ )
      }


     def log_(msg: String)(implicit logFunc: LogFunction) : Unit = macro logImpl
     def logImpl( c: Context )(msg: c.Expr[String])(logFunc: c.Expr[LogFunction]): c.Expr[Unit] = {
      import c.universe._
      reify( logFunc.splice.log(msg.splice, srcFile=fileImpl(c).splice, srcLine=lineImpl(c).splice, srcClass=classImpl(c).splice) )
     }
}


//--------------------------
//"main" project
import yourpackage.LogFunction

class LogImpl extends LogFunction {
  def log( msg: Any, srcFile: String = "", srcLine: Int = -1, srcClass:String = "") {
    println(List(msg,srcFile,srcLine,srcClass).mkString("|"))
  }
}

object testLog {

  def main(args: Array[String]): Unit = {

    implicit val defaultLog = new LogImpl

    import yourpackage.Macros.log_

    log_("blah")

  }

}

(请注意,我必须更正 log_ 的签名并稍微调整宏调用)

【讨论】:

  • 工作完美,谢谢。还有一个问题,如果我在最高级别的包对象中声明了隐含的 val,我怎样才能让所有子包都可以访问它?
  • 没关系,我在这里找到了答案:stackoverflow.com/questions/2830248/…
猜你喜欢
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多