【问题标题】:Scala macro: generate a class method based on ConfigScala宏:基于Config生成类方法
【发布时间】:2018-07-15 03:10:16
【问题描述】:

有没有办法在编译时基于 TypesafeConfig 对象在特定的类/特征/对象中生成方法?

例如,我有这个:

object Main {
  val config: Config = ConfigFactory.parseString(
    """
      |object {
      |  name = "go"
      |}
    """.stripMargin)

  generate(config)
}

而预期的结果是:

object Main {
  val config: Config = ConfigFactory.parseString(
    """
      |object {
      |  name = "go"
      |}
    """.stripMargin)

  def method: Unit = {
    print("go") /* the string comes from the config above */
  }
}

这个想法是能够在宏实现的范围内实例化 Config 对象,并使用它的属性来生成代码,例如(非常感谢 Dmytro Minin 的示例):

object GenerateMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    confObj = ... /* somehow get the real object based on macro input */        

    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
           ..$body

           def method: Unit = {
             print(s"${confObj.getString("object.name")}") /* use confObj's property to "embed" the value into the method generated */
           }
        }"""
    }
  }
}

【问题讨论】:

  • 不确定它是否是你要找的,但你可能想看看 pureconfig。

标签: scala scala-macros


【解决方案1】:

您可以创建宏注释:

宏/scr/main/scala/generate.scala

import com.typesafe.config.Config
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro paradise to expand macro annotations")
class generate extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GenerateMacro.impl
}

object GenerateMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._


    val confObj: Config = c.prefix.tree match {
      case q"new generate($config)" => c.eval(c.Expr[Config](/*c.untypecheck(*/config/*)*/))
    }

    println(confObj) //Config(SimpleConfigObject({"object":{"name":"go"}}))

    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
         ..$body

         def method: Unit = {
           print(${confObj.getString("object.name")})
         }
      }"""
    }
  }
}

并使用它:

core/scr/main/scala/Main.scala

@generate(com.typesafe.config.ConfigFactory.parseString(
  """
    |object {
    |  name = "go"
    |}
  """.stripMargin))
object Main

那你就可以了

Main.method //go

build.sbt

scalaVersion := "2.12.6"

lazy val commonSettings = Seq(
  addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
)

lazy val macros: Project = (project in file("macros")).settings(
  commonSettings,
  libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
)

lazy val core: Project = (project in file("core")).aggregate(macros).dependsOn(macros).settings(
  commonSettings,
  libraryDependencies += "com.typesafe" % "config" % "1.3.2"
)

【讨论】:

  • 谢谢,伙计,但您提供的解决方案并不是我想要在这里实现的。我想将配置对象“传递”到宏实现级别,以便能够通过使用来自外部的配置来生成方法,而不是使用已生成的方法来使用 config 作为类成员。我的意思如下:当调用 GenerateMacro.impl 时,我想: - 以某种方式实例化 TypesafeConfig 对象 - 使用它的属性来生成方法(例如:不仅仅是 print(config.getString("object.name")) 而是像这样: val name = confObj.getString("object.name") print(s"$val"))
  • 要么将参数添加到注释:class generate(config: Config) extends StaticAnnotation ...,要么编写 def 宏而不是注释,如果这对你来说足够了:def generate(config: Config): Unit = macro generateImpl; def generateImpl(c: blackbox.Context)(config: c.Tree): c.Tree = ...
  • 其实这正是问题所在:有没有办法将config: c.Tree 转换为config: Config?例如,一个字符串参数是显式传递的("value")有没有办法通过使用Eval[String]来获取值,关于配置有什么建议吗?
  • val cnfg: Config = c.eval[Config](c.Expr(config))你是这个意思吗?
  • 有点,但在这种情况下,代码会产生异常:scala.tools.reflect.ToolBoxError: reflective toolbox has failed: cannot operate on trees that are already typed 并且代码非常小:def generateConfigImpl(c: blackbox.Context)(entity: c.Tree): c.Tree = { import c.universe._ val cnfg: Config = c.eval[Config](c.Expr(entity)) q"""println("go")""" }
猜你喜欢
  • 1970-01-01
  • 2015-05-07
  • 2016-01-07
  • 2016-05-02
  • 2019-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多