1. 宏项目

宏项目 

简介 

使用宏时会遇到一些常见问题。

  1. 编译器中的当前宏实现要求在使用宏实现之前先编译它们。解决方案通常是将宏放在子项目中或它们自己的配置中。
  2. 有时宏实现应与使用它们的代码一起分发,而有时宏实现不应分发。

页面其余部分将展示这些问题的示例解决方案。

定义项目关系 

宏实现将位于 macro/ 目录中的子项目中。core/ 目录中的核心项目将依赖此子项目并使用宏。此配置在以下构建定义中显示。build.sbt

lazy val commonSettings = Seq(
  scalaVersion := "2.12.18",
  organization := "com.example"
)
lazy val scalaReflect = Def.setting { "org.scala-lang" % "scala-reflect" % scalaVersion.value }

lazy val core = (project in file("core"))
  .dependsOn(macroSub)
  .settings(
    commonSettings,
    // other settings here
  )

lazy val macroSub = (project in file("macro"))
  .settings(
    commonSettings,
    libraryDependencies += scalaReflect.value
    // other settings here
  )

这指定宏实现位于 macro/src/main/scala/ 中,测试位于 macro/src/test/scala/ 中。它还表明我们需要依赖编译器来实现宏。例如,我们将使用来自 macrocosmdesugar 作为宏。macro/src/main/scala/demo/Demo.scala

package demo

import language.experimental.macros
import scala.reflect.macros.blackbox.Context

object Demo {

  // Returns the tree of `a` after the typer, printed as source code.
  def desugar(a: Any): String = macro desugarImpl

  def desugarImpl(c: Context)(a: c.Expr[Any]) = {
    import c.universe._

    val s = show(a.tree)
    c.Expr(
      Literal(Constant(s))
    )
  }
}

macro/src/test/scala/demo/Usage.scala:

package demo

object Usage {
   def main(args: Array[String]): Unit = {
      val s = Demo.desugar(List(1, 2, 3).reverse)
      println(s)
   }
}

然后可以在控制台中运行它

$ sbt
> macroSub/Test/run
scala.collection.immutable.List.apply[Int](1, 2, 3).reverse

实际测试可以像往常一样使用 macro/test 定义和运行。

主项目可以使用与测试相同的方式使用宏。例如,

core/src/main/scala/MainUsage.scala:

package demo

object Usage {
   def main(args: Array[String]): Unit = {
      val s = Demo.desugar(List(6, 4, 5).sorted)
      println(s)
   }
}
$ sbt
> core/run
scala.collection.immutable.List.apply[Int](6, 4, 5).sorted[Int](math.this.Ordering.Int)

公共接口 

有时,宏实现和宏使用应共享一些公共代码。在这种情况下,为公共代码声明另一个子项目,并让主项目和宏子项目依赖新的子项目。例如,上面的项目定义将如下所示

lazy val commonSettings = Seq(
  scalaVersion := "2.12.18",
  organization := "com.example"
)
lazy val scalaReflect = Def.setting { "org.scala-lang" % "scala-reflect" % scalaVersion.value }

lazy val core = (project in file("core"))
  .dependsOn(macroSub, util)
  .settings(
    commonSettings,
    // other settings here
  )

lazy val macroSub = (project in file("macro"))
  .dependsOn(util)
  .settings(
    commonSettings,
    libraryDependencies += scalaReflect.value
    // other settings here
  )

lazy util = (project in file("util"))
  .settings(
    commonSettings,
    // other setting here
  )

util/src/main/scala/ 中的代码可供 macroSubmain 项目使用。

分发 

要将宏代码与核心代码一起包含,请将宏子项目的二进制和源代码映射添加到核心项目中。并且还应该从核心项目依赖项中删除宏子项目。例如,上面的 core Project 定义现在将如下所示

lazy val core = (project in file("core"))
  .dependsOn(macroSub % "compile-internal, test-internal")
  .settings(
    commonSettings,
    // include the macro classes and resources in the main jar
    Compile / packageBin / mappings ++= (macroSub / Compile / packageBin / mappings).value,
    // include the macro sources in the main source jar
    Compile / packageSrc / mappings ++= (macroSub / Compile / packageSrc / mappings).value
  )

您可能希望禁用发布宏实现。这可以通过覆盖 publishpublishLocal 来完成,使其什么也不做

lazy val macroSub = (project in file("macro"))
  .settings(
    commonSettings,
    libraryDependencies += scalaReflect.value,
    publish := {},
    publishLocal := {}
  )

此处描述的技术也可能用于上一节中描述的公共接口。