我定义了以下宏来从当前位置获取文件、行和对象/类: 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_") )
}
但同样,这个宏需要在项目之前编译,其中定义了日志函数本身...所以我不知道如何解决编译依赖循环...
关于如何做到这一点有什么建议吗? 谢谢
最佳答案
禁止使用 macro annotations (这必然会显着改变您的 API 语法),您必须面对的问题是您需要日志函数的类型检查标识符。
因为您无法导入整个 log
实现,解决方案是:
- 将方法包装到特征中,
- 在“宏”项目中定义此特征,
- 向
log_
添加隐式参数方法, - 在您的“主”项目中,创建此特征的实现,并在
implicit val
中实例化此实现。在您想要使用log_
的任何地方都可见宏(例如在包对象中)。
当然,您也可以使用简单的 FunctionN
在这里并避免特征定义和实现,但这样您就可以避免与其他相同类型的隐式发生潜在的冲突。
一般来说,您的代码将类似于以下内容:
//"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_
的签名并稍微调整宏调用)
关于Scala 宏的快捷方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22019910/