編譯時操作
scala.compiletime
套件
scala.compiletime
套件包含提供對值執行編譯時間運算的支援的輔助定義。以下說明這些定義。
constValue
和 constValueOpt
constValue
是產生型別所表示的常數值的函數,或者如果型別不是常數型別,則會產生編譯時間錯誤。
import scala.compiletime.constValue
import scala.compiletime.ops.int.S
transparent inline def toIntC[N]: Int =
inline constValue[N] match
case 0 => 0
case _: S[n1] => 1 + toIntC[n1]
inline val ctwo = toIntC[2]
constValueOpt
與 constValue
相同,但會傳回 Option[T]
,使我們能夠處理值不存在的情況。請注意,S
是某個單例型別的後繼型別的型別。例如,型別 S[1]
是單例型別 2
。
由於元組不是常數型別,即使其組成部分是,因此有 constValueTuple
,它會針對元組型別 (X1, ..., Xn)
傳回元組值 (constValue[X1], ..., constValue[Xn])
。
erasedValue
到目前為止,我們已經看到內聯方法將術語(元組和整數)作為參數。如果我們想根據類型來區分基本情況呢?例如,我們希望能夠編寫一個函數 defaultValue
,給定一個類型 T
,如果存在,則選擇性地返回 T
的預設值。我們已經可以使用重寫匹配表達式和一個簡單的輔助函數 scala.compiletime.erasedValue
來表達這一點,其定義如下
def erasedValue[T]: T
erasedValue
函數假裝返回其類型參數 T
的值。呼叫此函數將永遠導致編譯時期錯誤,除非在內聯時從程式碼中移除呼叫。
使用 erasedValue
,我們可以將 defaultValue
定義如下
import scala.compiletime.erasedValue
transparent inline def defaultValue[T] =
inline erasedValue[T] match
case _: Byte => Some(0: Byte)
case _: Char => Some(0: Char)
case _: Short => Some(0: Short)
case _: Int => Some(0)
case _: Long => Some(0L)
case _: Float => Some(0.0f)
case _: Double => Some(0.0d)
case _: Boolean => Some(false)
case _: Unit => Some(())
case _ => None
然後
val dInt: Some[Int] = defaultValue[Int]
val dDouble: Some[Double] = defaultValue[Double]
val dBoolean: Some[Boolean] = defaultValue[Boolean]
val dAny: None.type = defaultValue[Any]
作為另一個範例,考慮以下 toInt
的類型層級版本:給定表示皮亞諾數的類型,傳回對應的整數值。考慮如上文內聯匹配區段中的數字定義。以下是 toIntT
的定義方式
transparent inline def toIntT[N <: Nat]: Int =
inline scala.compiletime.erasedValue[N] match
case _: Zero.type => 0
case _: Succ[n] => toIntT[n] + 1
inline val two = toIntT[Succ[Succ[Zero.type]]]
erasedValue
是 erased
方法,因此無法使用且沒有執行時期行為。由於 toIntT
對 N
的靜態類型執行靜態檢查,因此我們可以安全地使用它來檢查其回傳類型(在本例中為 S[S[Z]]
)。
error
error
方法用於在內聯擴充期間產生使用者定義的編譯錯誤。它具有以下簽章
inline def error(inline msg: String): Nothing
如果內聯擴充導致呼叫 error(msgStr)
,編譯器會產生包含給定 msgStr
的錯誤訊息。
import scala.compiletime.{error, codeOf}
inline def fail() =
error("failed for a reason")
fail() // error: failed for a reason
或
inline def fail(inline p1: Any) =
error("failed on: " + codeOf(p1))
fail(identity("foo")) // error: failed on: identity[String]("foo")
scala.compiletime.ops
套件
scala.compiletime.ops
套件包含提供對單例類型進行基本運算支援的類型。例如,scala.compiletime.ops.int.*
提供對兩個單例 Int
類型進行乘法的支援,而 scala.compiletime.ops.boolean.&&
提供對兩個 Boolean
類型的連接支援。當 scala.compiletime.ops
中類型的所有參數都是單例類型時,編譯器可以評估運算結果。
import scala.compiletime.ops.int.*
import scala.compiletime.ops.boolean.*
val conjunction: true && true = true
val multiplication: 3 * 5 = 15
這些單例操作類型中的許多類型旨在用作中綴(如 SLS §3.2.10 中所示)。
由於類型別名具有與其術語級別等效項相同的優先順序規則,因此這些操作會與預期的優先順序規則組合。
import scala.compiletime.ops.int.*
val x: 1 + 2 * 3 = 7
操作類型位於以左手邊參數類型命名的套件中:例如,scala.compiletime.ops.int.+
表示兩個數字的加法,而 scala.compiletime.ops.string.+
表示字串串接。若要同時使用這兩個類型並將它們區分開來,比對類型可以傳送至正確的實作
import scala.compiletime.ops.*
import scala.annotation.infix
type +[X <: Int | String, Y <: Int | String] = (X, Y) match
case (Int, Int) => int.+[X, Y]
case (String, String) => string.+[X, Y]
val concat: "a" + "b" = "ab"
val addition: 1 + 1 = 2
有選擇性地傳送給定值
新的 summonFrom
建構函式讓隱式搜尋可在函式內容中使用。若要解決建立正確集合的問題,可以使用下列方式
import scala.compiletime.summonFrom
inline def setFor[T]: Set[T] = summonFrom {
case ord: Ordering[T] => new TreeSet[T]()(using ord)
case _ => new HashSet[T]
}
summonFrom
呼叫會將模式比對封閉作為引數。封閉中的所有模式都是 識別碼:類型
形式的類型歸屬。
模式會依序嘗試。第一個模式為 x: T
的情況,其中可以傳送類型為 T
的內容值。
或者,也可以使用模式約束的給定值實例,這可以避免使用明確的 using 子句。例如,setFor
也可以表述如下
import scala.compiletime.summonFrom
inline def setFor[T]: Set[T] = summonFrom {
case given Ordering[T] => new TreeSet[T]
case _ => new HashSet[T]
}
summonFrom
應用程式必須在編譯時縮減。
因此,如果給定值實例 Ordering[String]
在隱式範圍內,則上述程式碼會傳回 TreeSet[String]
的新實例。此類實例定義在 Ordering
的伴隨物件中,因此永遠只會有一個。
summon[Ordering[String]] // Proves that an Ordering[String] is in scope
println(setFor[String].getClass) // prints class scala.collection.immutable.TreeSet
注意: summonFrom
應用程式可能會引發歧義錯誤。考慮以下程式碼,其中兩個給定值在 A
類型的範圍內。如果套用 f
,則 f
中的模式比對會引發歧義錯誤。
class A
given a1: A = new A
given a2: A = new A
inline def f: Any = summonFrom {
case given _: A => ??? // error: ambiguous givens
}
summonInline
簡寫 summonInline
提供一種簡單的方法來撰寫 summon
,此方法會延遲到呼叫內聯時才執行。與 summonFrom
不同,如果找不到召喚類型的特定執行個體,summonInline
也會產生隱式找不到的錯誤。
import scala.compiletime.summonInline
import scala.annotation.implicitNotFound
@implicitNotFound("Missing One")
trait Missing1
@implicitNotFound("Missing Two")
trait Missing2
trait NotMissing
given NotMissing = ???
transparent inline def summonInlineCheck[T <: Int](inline t : T) : Any =
inline t match
case 1 => summonInline[Missing1]
case 2 => summonInline[Missing2]
case _ => summonInline[NotMissing]
val missing1 = summonInlineCheck(1) // error: Missing One
val missing2 = summonInlineCheck(2) // error: Missing Two
val notMissing : NotMissing = summonInlineCheck(3)
參考
如需有關編譯時期運算的更多資訊,請參閱 PR #4768,其中說明如何將 summonFrom
的前身(隱式比對)用於類型層次程式設計和程式碼專門化,以及 PR #7201,其中說明新的 summonFrom
語法。