1. Macro 專案

Macro 專案 

簡介 

使用巨集時會出現一些常見問題。

  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/ 中。它也顯示了我們需要對編譯器有相依性以進行巨集實作。作為巨集範例,我們將使用來自 macrocosmdesugarmacro/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 專案定義現在會如下所示

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 := {}
  )

這裡描述的技術也可以用於上一節中描述的通用介面。