巨集函式庫必須從頭重新實作。
在開始之前,您應該熟悉 移植 sbt 專案 教學課程中所述的 Scala 3 遷移。本教學課程的目的是跨建現有的 Scala 2.13 巨集函式庫,使其同時可在 Scala 3 和 Scala 2.13 中使用。
混合巨集 的替代解決方案在 下一個教學課程 中說明。建議您閱讀這兩種解決方案,以選擇最適合您需求的技術。
簡介
為了說明本教學課程,我們將考慮下面定義的最小巨集函式庫。
// build.sbt
lazy val example = project
.in(file("example"))
.settings(
scalaVersion := "2.13.11",
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value
)
)
// example/src/main/scala/location/Location.scala
package location
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
case class Location(path: String, line: Int)
object Macros {
def location: Location = macro locationImpl
private def locationImpl(c: Context): c.Tree = {
import c.universe._
val location = typeOf[Location]
val line = Literal(Constant(c.enclosingPosition.line))
val path = Literal(Constant(c.enclosingPosition.source.path))
q"new $location($path, $line)"
}
}
您應該會發現一些與您的函式庫類似的部分:一個或多個巨集方法,在我們的案例中,location
方法是透過使用巨集 Context
並從此內容傳回 Tree
來實作的。
我們可以使用 sbt 提供的 跨建 技術,讓 Scala 3 使用者可以使用此函式庫。
主要概念是建置成品兩次,並發布兩個版本
example_2.13
給 Scala 2.13 使用者example_3
給 Scala 3 使用者
1. 設定跨版本編譯
你可以將 Scala 3 加入專案的 crossScalaVersions
清單
crossScalaVersions := Seq("2.13.11", "3.3.1")
在 Scala 3 中,scala-reflect
相依性沒有用處。使用類似以下方式有條件地移除它
// build.sbt
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) => Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value
)
case _ => Seq.empty
}
}
重新載入 sbt 後,你可以執行 ++3.3.1
切換到 Scala 3 環境。你可以隨時執行 ++2.13.11
回到 Scala 2.13 環境。
2. 在特定版本的原始碼目錄中重新排列程式碼
如果你嘗試使用 Scala 3 編譯,你應該會看到一些類似以下的錯誤
sbt:example> ++3.3.1
sbt:example> example / compile
[error] -- Error: /example/src/main/scala/location/Location.scala:15:35
[error] 15 | val location = typeOf[Location]
[error] | ^
[error] | No TypeTag available for location.Location
[error] -- Error: /example/src/main/scala/location/Location.scala:18:4
[error] 18 | q"new $location($path, $line)"
[error] | ^
[error] |Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html
[error] |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler
為了提供 Scala 3 的替代方案,同時保留 Scala 2 的實作,我們將在特定版本的原始碼目錄中重新排列程式碼。所有無法由 Scala 3 編譯器編譯的程式碼都移到 src/main/scala-2
資料夾。
特定 Scala 版本的原始碼目錄是 sbt 的預設功能。在 sbt 文件 中了解更多資訊。
在我們的範例中,Location
類別留在 src/main/scala
資料夾,但 Macros
物件移到 src/main/scala-2
資料夾
// example/src/main/scala-2/location/Macros.scala
package location
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
object Macros {
def location: Location = macro locationImpl
private def locationImpl(c: Context): c.Tree = {
import c.universe._
val location = typeOf[Location]
val line = Literal(Constant(c.enclosingPosition.line))
val path = Literal(Constant(c.enclosingPosition.source.path))
q"new $location($path, $line)"
}
}
現在我們可以在 src/main/scala-3
資料夾中初始化每個 Scala 3 巨集定義。它們必須與 Scala 2.13 對應項具有完全相同的簽章。
// example/src/main/scala-3/location/Macros.scala
package location
object Macros:
def location: Location = ???
3. 實作 Scala 3 巨集
沒有神奇的公式可以將 Scala 2 巨集移植到 Scala 3。需要瞭解新的 巨集編程 功能。
我們最終提出這個實作
// example/src/main/scala-3/location/Macros.scala
package location
import scala.quoted.{Quotes, Expr}
object Macros:
inline def location: Location = ${locationImpl}
private def locationImpl(using quotes: Quotes): Expr[Location] =
import quotes.reflect.Position
val pos = Position.ofMacroExpansion
val file = Expr(pos.sourceFile.jpath.toString)
val line = Expr(pos.startLine + 1)
'{new Location($file, $line)}
4. 跨版本驗證巨集
加入一些測試非常重要,以檢查巨集方法在兩個 Scala 版本中是否都能正常運作。
在我們的範例中,我們加入一個單元測試。
現在你應該可以在兩個版本中執行測試。
sbt:example> ++2.13.11
sbt:example> example / test
location.MacrosSpec:
+ location
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success]
sbt:example> ++3.3.1
sbt:example> example / test
location.MacrosSpec:
+ location
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success]
最後概述
你的巨集專案現在應該包含下列原始碼檔案
src/main/scala/*.scala
:跨相容類別src/main/scala-2/*.scala
:巨集方法的 Scala 2 實作src/main/scala-3/*.scala
:巨集方法的 Scala 3 實作src/test/scala/*.scala
:常見測試
現在,您可以透過建立兩個版本來發布您的程式庫
example_2.13
給 Scala 2.13 使用者example_3
給 Scala 3 使用者