マクロを使用する際にいくつかの一般的な問題が発生します。
このページの残りの部分では、これらの問題に対する解決策の例を示します。
マクロの実装は、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/
に配置されることが指定されます。また、マクロの実装にはコンパイラへの依存関係が必要であることも示しています。マクロの例として、macrocosm の desugar
を使用します。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/
のコードは、macroSub
プロジェクトと main
プロジェクトの両方で使用できます。
マクロコードをコアコードに含めるには、マクロサブプロジェクトからコアプロジェクトへのバイナリおよびソースマッピングを追加します。また、マクロサブプロジェクトは公開時にコアプロジェクトの依存関係から削除する必要があります。たとえば、上記の 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
)
マクロ実装の公開を無効にしたい場合があります。これは、publish
および publishLocal
をオーバーライドして何も行わないようにすることで行われます。
lazy val macroSub = (project in file("macro"))
.settings(
commonSettings,
libraryDependencies += scalaReflect.value,
publish := {},
publishLocal := {}
)
ここで説明する手法は、前のセクションで説明した共通インターフェースにも使用できます。