Quasiquotes

定義和導入詳細資料

語言
此文件頁面特定於 Scala 2 中發布的功能,這些功能已在 Scala 3 中移除或由其他功能取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用的是 Scala 2。

Denys Shabalin 實驗性質

修飾詞

除了套件和套件物件之外,每個定義都有一個相關聯的修飾詞物件,其中包含下列資料

  1. FlagSet,一組特徵化給定定義的位元。
  2. 名稱內部私有(例如 foo 中的 private[foo] def f)。
  3. 註解清單。

準引號讓你可以透過對 ModifiersFlagSet 和註解取消引號的原生支援,輕鬆使用這些欄位

scala> val f1 = q"${Modifiers(PRIVATE | IMPLICIT)} def f"
f1: universe.DefDef = implicit private def f: scala.Unit

scala> val f2 = q"$PRIVATE $IMPLICIT def f"
f2: universe.DefDef = implicit private def f: scala.Unit

scala> val f3 = q"private implicit def f"
f3: universe.DefDef = implicit private def f: scala.Unit

所有這些準引號都會產生等效的樹狀結構。也可以將未引號的旗標與原始碼中內嵌提供的旗標結合,但未引號的旗標應在內嵌旗標之前使用

scala> q"$PRIVATE implicit def foo"
res10: universe.DefDef = implicit private def foo: scala.Unit

scala> q"implicit $PRIVATE def foo"
<console>:32: error: expected start of definition
              q"implicit $PRIVATE def foo"
                         ^

若要提供定義註解,必須取消引號一個新形狀的樹狀結構

scala> val annot = q"new foo(1)"
annot: universe.Tree = new Foo(1)

scala> val f4 = q"@$annot def f"
f4: universe.DefDef = @new foo(1) def f: scala.Unit

scala> val f5 = q"@foo(1) def f"
f5: universe.DefDef = @new foo(1) def f: scala.Unit

在解構中,可以萃取 Modifiers 或註解,但無法個別萃取旗標

scala> val q"$mods def f" = q"@foo implicit def f"
mods: universe.Modifiers = Modifiers(<deferred> implicit, , Map())

scala> val q"@..$annots implicit def f" = q"@foo @bar implicit def f"
annots: List[universe.Tree] = List(new foo(), new bar())

考量到定義可能包含在類型檢查期間新增到樹狀結構的各種低階旗標,建議總是萃取完整的修改器,否則你的模式可能無法窮舉。如果你不關心它們,只要使用萬用字元即可

scala> val q"$_ def f" = q"@foo @bar implicit def f"

範本

範本是定義樹狀結構中常見的抽象,用於新的表達式、類別、特質、物件和套件物件。雖然目前沒有插補器,但我們可以使用新表達式的範例來說明其結構(類似的處理方式會套用於所有其他有範本的樹狀結構)

q"new { ..$earlydefns } with ..$parents { $self => ..$stats }"

範本包含

  1. 早期定義。一個 valtype 定義清單。類型定義仍然允許,但已棄用,未來將會移除

     scala> val withx = q"new { val x = 1 } with RequiresX"
     withx: universe.Tree = ...
    
     scala> val q"new { ..$earlydefns } with RequiresX" = withx
     earlydefns: List[universe.Tree] = List(val x = 1)
    
  2. 父項清單。一個類型識別項清單,其中清單中的第一個類型識別項可以有選擇性的類型和值引數。這是因為第一個父項必須是類別,而後續的父項只是尚未接受引數的特質

     scala> val q"new ..$parents"  = q"new Foo(1) with Bar[T]"
     parents: List[universe.Tree] = List(Foo(1), Bar[T])
    

    第一個父項有一個不尋常的形狀,是術語和類型樹狀結構的組合

     scala> val q"${tq"$name[..$targs]"}(...$argss)" = parents.head
     name: universe.Tree = Foo
     targs: List[universe.Tree] = List()
     argss: List[List[universe.Tree]] = List(List(1))
    

    其他父項只是單純的類型樹狀結構

     scala> val tq"$name[..$targs]" = parents.tail.head
     name: universe.Tree = Bar
     targs: List[universe.Tree] = List(T)
    
  3. 自我類型定義。一個 val 定義,可用於定義 this 的別名,並透過 tpt 提供自我類型

     scala> val q"new { $self => }" = q"new { self: T => }"
     self: universe.ValDef = private val self: T = _
    
     scala> val q"$mods val $name: $tpt" = self
     mods: universe.Modifiers = Modifiers(private, , Map())
     name: universe.TermName = self
     tpt: universe.Tree = T
    
  4. 主體陳述清單。

     scala> val q"new { ..$body }" = q"new { val x = 1; def y = 'y }"
     body: List[universe.Tree] = List(val x = 1, def y = scala.Symbol("y"))
    

Val 和 Var 定義

valvar 分別允許您定義不可變值和可變變數。此外,它們可用於表示 函數類別方法 參數。

每個 valvar 都包含四個組成部分:修飾詞、名稱、類型樹和右側

scala> val valx = q"val x = 2"
valx: universe.ValDef = val x = 2

scala> val q"$mods val $name: $tpt = $rhs" = valx
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = x
tpt: universe.Tree = <type ?>
rhs: universe.Tree = 2

如果 val 的類型未由使用者明確指定,則會使用 空類型 作為 tpt

valvar 是不相交的(它們彼此不匹配)

scala> val q"$mods val $name: $tpt = $rhs" = q"var x = 2"
scala.MatchError: var x = 2 (of class scala.reflect.internal.Trees$ValDef)
  ... 32 elided

Vars 在其修飾詞中始終具有 MUTABLE 標誌

scala> val q"$mods var $name: $tpt = $rhs" = q"var x = 2"
mods: universe.Modifiers = Modifiers(<mutable>, , Map())
name: universe.TermName = x
tpt: universe.Tree = <type ?>
rhs: universe.Tree = 2

模式定義

模式定義允許您使用 Scala 模式比對功能來定義變數。與 valvar 定義不同,模式定義不是一級的,它們透過常規 valvar 和模式比對的組合來表示

scala> val patdef = q"val (x, y) = (1, 2)"
patdef: universe.Tree =
{
  <synthetic> <artifact> private[this] val x$2 = scala.Tuple2(1, 2): @scala.unchecked match {
    case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y)
  };
  val x = x$2._1;
  val y = x$2._2;
  ()
}

此表示法對此類定義的使用有一些副作用

  1. 由於單一定義通常會被簡化為多個較低層級的定義,因此您必須始終使用反引號拼接將模式定義反引號化到其他樹中

     scala> val tupsum = q"..$patdef; a + b"
     tupsum: universe.Tree =
     {
       <synthetic> <artifact> private[this] val x$3 = scala.Tuple2(1, 2): @scala.unchecked match {
         case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y)
       };
       val x = x$3._1;
       val y = x$3._2;
       a.$plus(b)
     }
    

    否則,如果使用常規反引號化,這些定義將會嵌套在一個區塊中,這會使它們在它們應被使用的範圍中不可見

     scala> val wrongtupsum = q"$patdef; a + b"
     wrongtupsum: universe.Tree =
     {
       {
         <synthetic> <artifact> private[this] val x$3 = scala.Tuple2(1, 2): @scala.unchecked match {
           case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y)
         };
         val x = x$3._1;
         val y = x$3._2;
         ()
       };
       a.$plus(b)
     }
    
  2. 只能建構模式定義,不能解構它們。

模式定義的一般形式包含修飾詞、模式、指定類型和右側

q"$mods val $pat: $tpt = $rhs"

類似地,也可以建構可變模式定義

q"$mods var $pat: $tpt = $rhs"

類型定義

類型定義有兩種可能的形狀:抽象類型定義和別名類型定義。

抽象類型定義具有下列形狀

scala> val q"$mods type $name[..$tparams] >: $low <: $high" =
           q"type Foo[T] <: List[T]"
mods: universe.Modifiers = Modifiers(<deferred>, , Map())
name: universe.TypeName = Foo
tparams: List[universe.TypeDef] = List(type T)
low: universe.Tree = <empty>
high: universe.Tree = List[T]

只要其中一個界限不可用,就會表示為空樹。這裡每個類型參數本身都是類型定義。

另一種形式的類型定義是類型別名

scala> val q"$mods type $name[..$args] = $tpt" =
           q"type Foo[T] = List[T]"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TypeName = Foo
args: List[universe.TypeDef] = List(type T)
tpt: universe.Tree = List[T]

由於類型別名和抽象類型的低階統一表示,一個會匹配另一個

scala> val q"$mods type $name[..$args] = $tpt" = q"type Foo[T] <: List[T]"
mods: universe.Modifiers = Modifiers(<deferred>, , Map())
name: universe.TypeName = Foo
args: List[universe.TypeDef] = List(type T)
tpt: universe.Tree =  <: List[T]

其中 tpt 具有 TypeBoundsTree(low, high) 形狀。

方法定義

每個方法都包含修飾詞、名稱、類型參數、值參數、回傳類型和主體

scala> val q"$mods def $name[..$tparams](...$paramss): $tpt = $body" = q"def f = 1"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = f
tparams: List[universe.TypeDef] = List()
paramss: List[List[universe.ValDef]] = List()
tpt: universe.Tree = <type ?>
body: universe.Tree = 1

類型參數是類型定義,而值參數是val 定義。推論的回傳類型表示為空類型。如果方法的主體是空表達式,表示該方法是抽象的。

或者,您也可以解構參數,將隱含參數和非隱含參數分開

scala> val q"def g(...$paramss)(implicit ..$implparams) = $body" =
           q"def g(x: Int)(implicit y: Int) = x + y"
paramss: List[List[universe.ValDef]] = List(List(val x: Int = _))
implparams: List[universe.ValDef] = List(implicit val y: Int = _)
body: universe.Tree = x.$plus(y)

如果方法沒有任何隱含參數,這種處理參數的方式仍然有效,而且 implparams 會被萃取為空清單

scala> val q"def g(...$paramss)(implicit ..$implparams) = $rhs" =
           q"def g(x: Int)(y: Int) = x + y"
paramss: List[List[universe.ValDef]] = List(List(val x: Int = _), List(val y: Int = _))
implparams: List[universe.ValDef] = List()
body: universe.Tree = x.$plus(y)

次要建構函式定義

次要建構函式是具有下列形狀的特殊方法類型

scala> val q"$mods def this(...$paramss) = this(...$argss)" =
           q"def this() = this(0)"
mods: universe.Modifiers = Modifiers(, , Map())
paramss: List[List[universe.ValDef]] = List(List())
argss: List[List[universe.Tree]] = List(List(0))

由於樹的低階底層表示,次要建構函式表示為具有 termNames.CONSTRUCTOR 名稱的特殊方法類型

scala> val q"$mods def $name[..$tparams](...$paramss): $tpt = $body"
         = q"def this() = this(0)"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = <init>
tparams: List[universe.TypeDef] = List()
paramss: List[List[universe.ValDef]] = List(List())
tpt: universe.Tree = <type ?>
body: universe.Tree = <init>(0)

類別定義

類別具有下列結構

q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"

正如你可能已經看到的,extends 之後的右半部分只是一個 範本。除了它和修飾詞之外,類別還有一個主要建構函數,它由建構函數修飾詞、類型和值參數組成,這些參數的行為非常類似於 方法 修飾詞和參數。

特質定義

在語法上,特質與 類別 非常相似,除了值參數和建構函數修飾詞之外。

 q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }"

處理方式的一個重要區別是由於 SI-8399 造成的,這是因為為特質設定了 INTERFACE 標記。由於只有抽象成員,特質模式可能無法匹配。

scala> val q"trait $name { ..$stats }" = q"trait X { def x: Int }"
scala.MatchError: ...

一個解決方法是始終使用萬用字元模式提取修飾詞。

scala> val q"$_ trait $name { ..$stats }" = q"trait X { def x: Int }"
name: universe.TypeName = X
stats: List[universe.Tree] = List(def x: Int)

物件定義

在語法上,物件與沒有建構函數的 類別 非常相似。

q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }"

套件定義

套件是組織原始碼的基本原語。你可以用準引號表示它們,如下所示:

scala> val pack = q"package mycorp.myproj { class MyClass }"
pack: universe.PackageDef =
package mycorp.myproj {
  class MyClass extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    }
  }
}

scala> val q"package $ref { ..$body }" = pack
ref: universe.RefTree = mycorp.myproj
body: List[universe.Tree] =
List(class MyClass extends scala.AnyRef {
  def <init>() = {
    super.<init>();
    ()
  }
})

準引號不支援通常用於原始檔標頭中的內嵌套件定義語法(但它在 AST 方面等同於受支援的語法)。

套件物件定義

套件物件是套件和物件之間的交叉。

q"package object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }"

除了沒有 修飾詞 之外,所有處理屬性都等同於物件的屬性。

儘管套件和常規物件在語法上似乎非常相似,但它們並不相互匹配。

scala> val q"$mods object $name" = q"package object O"
scala.MatchError: ...

scala> val q"package object $name" = q"object O"
scala.MatchError: ...

在內部,它們被表示為巢狀在具有給定名稱的套件中的物件。

scala> val P = q"package object P"
P: universe.PackageDef =
package P {
  object `package` extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    }
  }
}

這也表示你可以將套件物件與套件匹配。

此頁面的貢獻者