準引號

表達式詳細資料

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

Denys Shabalin 實驗性

空值

q"" 用於表示樹的某些部分並非由使用者提供

  1. 沒有右邊的 ValVarDef 會將其設定為 q""
  2. 沒有界限的抽象類型定義會將其設定為 q""
  3. 沒有 finally 子句的 Try 表達式會將其設定為 q""
  4. 沒有防護的 Case 子句會將其設定為 q""

預設的 toString 會將 q"" 格式化為 <empty>

文字

Scala 有許多內建的預設文字

q"1", q"1L"              // integer literals
q"1.0f", q"1.0", q"1.0d" // floating point literals
q"true", q"false"        // boolean literals
q"'c'"                   // character literal
q""" "string" """        // string literal
q"'symbol"               // symbol literal
q"null"                  // null literal
q"()"                    // unit literal

除了符號(有不同的表示方式)之外,所有這些值都是 Literal 型別

scala> val foo = q"'foo"
foo: universe.Tree = scala.Symbol("foo")

感謝 提升,您也可以直接從對應型別的值輕鬆建立文字樹

scala> val x = 1
scala> val one = q"$x"
one: universe.Tree = 1

這對所有文字型別(請參閱 標準可提升項目)都有相同的作用,但 Null 除外。不支援將 null 值提升到 Null 型別;如果您真的想要建立 null 文字,請使用 q"null"

scala> val x = null
scala> q"$x"
<console>:31: error: Can't unquote Null, bottom type values often indicate programmer mistake
              q"$x"
                 ^

在解構期間,您可以使用 解除Literal 樹中提取值

scala> val q"${x: Int}" = q"1"
x: Int = 1

同樣地,它會與所有文字類型一起使用,除了 Null。(請參閱 標準解除

識別碼和選擇

識別碼和成員選擇是讓您可以參考其他定義的兩個基本原語。它們兩個的組合也稱為 RefTree

每個術語識別碼都由其名稱定義,以及是否反引號

scala> val name = TermName("Foo")
name: universe.TermName = Foo

scala> val foo = q"$name"
foo: universe.Ident = Foo

scala> val backquoted = q"`$name`"
backquoted: universe.Ident = `Foo`

儘管反引號和非反引號識別碼可能指的是同一件事,但它們在語法上並不等效

scala> val q"`Foo`" = q"Foo"
scala.MatchError: Foo (of class scala.reflect.internal.Trees$Ident)
  ... 32 elided

這是因為反引號識別碼在模式修補中具有不同的語義。

除了使用給定名稱匹配識別碼外,您還可以借助 解除 提取其名稱值

scala> val q"${name: TermName}" = q"Foo"
name: universe.TermName = Foo

在此處,名稱歸屬很重要,因為沒有它,您將獲得等於常規模式變數繫結的模式。

同樣地,您可以建立和提取成員選擇

scala> val member = TermName("bar")
member: universe.TermName = bar

scala> val q"foo.$name" = selected
name: universe.TermName = bar

Super 和 This

人們可以使用 thissuper 在繼承鏈中選擇精確的成員。

此樹支援下列變異

scala> val q"$name.this" = q"this"
name: universe.TypeName =

scala> val q"$name.this" = q"foo.this"
name: universe.TypeName = foo

因此,未限定的 q"this" 等於 q"${tpnme.EMPTY}.this"

同樣地,對於 super,我們有

scala> val q"$name.super[$qual].$field" = q"super.foo"
name: universe.TypeName =
qual: universe.TypeName =
field: universe.Name = foo

scala> val q"$name.super[$qual].$field" = q"super[T].foo"
name: universe.TypeName =
qual: universe.TypeName = T
field: universe.Name = foo

scala> val q"$name.super[$qual].$field" = q"other.super[T].foo"
name: universe.TypeName = other
qual: universe.TypeName = T
field: universe.Name = foo

應用和類型應用

值應用和類型應用是兩個基本部分,人們可以從中建構對 Scala 函數和方法的呼叫。讓我們假設我們想要處理對下列方法的函數呼叫

def f[T](xs: T*): List[T] = xs.toList

這可以用下列方法完成

scala> val apps = List(q"f[Int](1, 2)", q"f('a, 'b)")
scala> apps.foreach {
         case q"f[..$ts](..$args)" =>
           println(s"type arguments: $ts, value arguments: $args")
       }
type arguments: List(Int), value arguments: List(1, 2)
type arguments: List(), value arguments: List(scala.Symbol("a"), scala.Symbol("b"))

如您所見,無論是否存在特定類型應用,我們都可以匹配兩個呼叫。這是因為如果樹不是實際的類型應用,類型應用比對器會提取類型引數的空清單,使得可以統一處理兩種情況。

建議在使用類型參數比對函數時,務必包含類型應用程式,因為編譯器會在類型檢查期間插入它們,即使使用者沒有明確撰寫

scala> val q"$_; f[..$ts](..$args)" = toolbox.typecheck(q"""
         def f[T](xs: T*): List[T] = xs.toList
         f(1, 2, 3)
       """)
ts: List[universe.Tree] = List(Int)
args: List[universe.Tree] = List(1, 2, 3)

Scala 方法呼叫的其他重要功能是多個參數清單和隱式參數

def g(x: Int)(implicit y: Int) = x + y

在這裡,我們可能會取得一個或兩個後續值應用程式

scala> val apps = List(q"g(1)", q"g(1)(2)")
scala> apps.foreach {
         case q"g(...$argss)" if argss.nonEmpty =>
           println(s"argss: $argss")
       }
argss: List(List(1))
argss: List(List(1), List(2))

...$,在模式中,允許我們貪婪比對所有後續值應用程式。與類型參數比對器類似,需要小心,因為即使在沒有實際值應用程式的案例中,它也會永遠比對

scala> val q"g(...$argss)" = q"g"
argss: List[List[universe.Tree]] = List()

因此,建議使用更具體的模式,以檢查確保提取的 argss 不是空的

與類型參數類似,隱式值參數會在類型檢查期間自動推論

scala> val q"..$stats; g(...$argss)" = toolbox.typecheck(q"""
         def g(x: Int)(implicit y: Int) = x + y
         implicit val y = 3
         g(2)
       """)
stats: List[universe.Tree] = List(def g(x: Int)(implicit y: Int): Int = x.+(y), implicit val y: Int = 3)
argss: List[List[universe.Tree]] = List(List(2), List(y))

指定和更新

指定和更新是明確變異變數或集合的兩種相關方式

scala> val assign = q"x = 2"
assign: universe.Tree = x = 2

scala> val update = q"array(0) = 1"
update: universe.Tree = array.update(0, 1)

正如您所見,更新語法只是語法糖,表示為給定物件上的更新方法呼叫

儘管如此,準引號讓您可以根據使用者介面語法,統一解構它們

scala> List(assign, update).foreach {
         case q"$left = $right" =>
           println(s"left = $left, right = $right")
       }
left = x, right = 2
left = array(0), right = 1

其中 array(0) 具有與函數應用程式相同的 AST

另一方面,如果您想分別處理這兩個案例,可以使用以下更具體的模式

scala> List(assign, update).foreach {
         case q"${ref: RefTree} = $expr" =>
           println(s"assign $expr to $ref")
         case q"$obj(..$args) = $expr" =>
           println(s"update $obj at $args with $expr")
       }
assign 2 to x
update array at List(0) with 1

傳回

傳回 運算式用於從函數執行早期傳回

scala> val ret = q"return 2 + 2"
ret: universe.Return = return 2.$plus(2)

scala> val q"return $expr" = ret
expr: universe.Tree = 2.$plus(2)

擲回

擲回 運算式用於擲回可擲回物件

scala> val thr = q"throw new Exception"
thr: universe.Throw = throw new Exception()

scala> val q"throw $expr" = thr
expr: universe.Tree = new Exception()

歸因

歸因讓使用者可以註解中間運算式的類型

scala> val ascribed = q"(1 + 1): Int"
ascribed: universe.Typed = (1.$plus(1): Int)

scala> val q"$expr: $tpt" = ascribed
expr: universe.Tree = 1.$plus(1)
tpt: universe.Tree = Int

註解

表達式可以加上註解

scala> val annotated = q"(1 + 1): @positive"
annotated: universe.Annotated = 1.$plus(1): @positive

scala> val q"$expr: @$annot" = annotated
expr: universe.Tree = 1.$plus(1)
annot: universe.Tree = positive

重要的是,如果我們將註解與歸屬結合,這樣的模式將無法匹配

scala> val q"$expr: @$annot" = q"(1 + 1): Int @positive"
scala.MatchError: (1.$plus(1): Int @positive) (of class scala.reflect.internal.Trees$Typed)
  ... 32 elided

在這種情況下,我們需要將其解構為 歸屬,然後將 tpt 解構為 註解類型

元組

元組是具有內建使用者友善語法的異質資料結構。語法本身只是語法糖,對應到 scala.TupleN 呼叫

scala> val tup = q"(a, b)"
tup: universe.Tree = scala.Tuple2(a, b)

目前,元組僅支援到 22 的元數,但這只是一個實作限制,未來可能會解除。若要找出是否支援特定元數,請使用

scala> val `tuple 10 supported?` = definitions.TupleClass(10) != NoSymbol
tuple 10 supported?: Boolean = true

scala> val `tuple 23 supported?` = definitions.TupleClass(23) != NoSymbol
tuple 23 supported?: Boolean = false

儘管 Tuple1 類別存在,但沒有內建語法。表達式周圍的單括號不會改變其意義

scala> val inparens = q"(a)"
inparens: universe.Ident = a

Unit 視為零元組也很常見

scala> val elems = List.empty[Tree]
scala> val nullary = q"(..$elems)"
nullary: universe.Tree = ()

準引號也支援解構任意元數的元組

scala> val q"(..$elems)" = q"(a, b)"
elems: List[universe.Tree] = List(a, b)

此模式也匹配表達式為單元素元組

scala> val q"(..$elems)" = q"(a)"
elems: List[universe.Tree] = List(a)

以及 Unit 為零元組

scala> val q"(..$elems)" = q"()"
elems: List[universe.Tree] = List()

區塊

區塊是表達動作或繫結順序的基本原語。 q"..." 內插器等同於區塊。它允許你傳達多個表達式,以分號或換行符號分隔

scala> val t = q"a; b; c"
t: universe.Tree =
{
  a;
  b;
  c
}

q"{...}"q"..." 之間唯一的差別是它們如何處理只有單一元素的情況。 q"..." 永遠回傳元素本身,而如果單一元素不是表達式,區塊仍然是區塊

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

scala> val t = q"{ val x = 2 }"
t: universe.Tree =
{
  val x = 2;
  ()
}

區塊也可以使用 ..$ 壓平到其他區塊中

scala> val ab = q"a; b"
ab: universe.Tree =
{
  a;
  b
}

scala> val abc = q"..$ab; c"
abc: universe.Tree =
{
  a;
  b;
  c
}

相同的語法可用於解構區塊

scala> val q"..$stats" = q"a; b; c"
stats: List[universe.Tree] = List(a, b, c)

解構總是會傳回區塊中使用者定義的內容

scala> val q"..$stats" = q"{ val x = 2 }"
stats: List[universe.Tree] = List(val x = 2)

由於單一元素區塊會自動扁平化為表達式,因此表達式本身被視為單一元素區塊

scala> val q"..$stats" = q"foo"
stats: List[universe.Tree] = List(foo)

除了空樹狀結構,它不被視為區塊

scala> val q"..$stats" = q""
scala.MatchError: <empty> (of class scala.reflect.internal.Trees$EmptyTree$)
  ... 32 elided

零元素區塊等於合成單位(由編譯器插入,而非使用者撰寫的單位)

scala> val q"..$stats" = q"{}"
stats: List[universe.Tree] = List()

scala> val syntheticUnit = q"..$stats"
syntheticUnit: universe.Tree = ()

此類單位用於 ifelse 分支,以及 case 子句 的空主體,讓這些情況能像處理零元素區塊一樣方便。

如果

if 表達式有兩種變體:有 else 子句和沒有 else 子句

scala> val q"if ($cond) $thenp else $elsep" = q"if (true) a else b"
cond: universe.Tree = true
thenp: universe.Tree = a
elsep: universe.Tree = b

scala> val q"if ($cond) $thenp else $elsep" = q"if (true) a"
cond: universe.Tree = true
thenp: universe.Tree = a
elsep: universe.Tree = ()

遺失的 else 子句等於包含合成單位文字 (空區塊) 的 else 子句。

模式比對

模式比對是 Scala 的基石功能,讓您可以將值解構為其組成部分

q"$expr match { case ..$cases } "

其中 expr 是非空表達式,每個 case 都以 cq"..." 引用表示

cq"$pat if $expr => $expr"

這兩種形式的組合,讓您可以建構和解構任意模式比對

scala> val q"$expr match { case ..$cases }" =
           q"foo match { case _: Foo => 'foo case _ => 'notfoo }"
expr: universe.Tree = foo
cases: List[universe.CaseDef] = List(case (_: Foo) => scala.Symbol("foo"), case _ => scala.Symbol("notfoo"))

scala> val cq"$pat1 => $body1" :: cq"$pat2 => $body2" :: Nil = cases
pat1: universe.Tree = (_: Foo)
body1: universe.Tree = scala.Symbol("foo")
pat2: universe.Tree = _
body2: universe.Tree = scala.Symbol("notfoo")

沒有主體的 case 子句等於包含合成單位文字 (空區塊) 的 case 子句

scala> val cq"$pat if $expr1 => $expr2" = cq"_ =>"
pat: universe.Tree = _
expr1: universe.Tree = <empty>
expr2: universe.Tree = ()

沒有防護措施會以 空表達式 表示。

嘗試

try 表達式用於處理可能的錯誤狀況,並透過 finally 確保一致的狀態。錯誤處理案例和 finally 子句都是選用的。

scala> val q"try $a catch { case ..$b } finally $c" = q"try t"
a: universe.Tree = t
b: List[universe.CaseDef] = List()
c: universe.Tree = <empty>

scala> val q"try $a catch { case ..$b } finally $c" =
           q"try t catch { case _: C => }"
a: universe.Tree = t
b: List[universe.CaseDef] = List(case (_: C) => ())
c: universe.Tree = <empty>

scala> val q"try $a catch { case ..$b } finally $c" =
           q"try t finally f"
a: universe.Tree = t
b: List[universe.CaseDef] = List()
c: universe.Tree = f

類似於 樣式比對,案例可以進一步使用 cq"..." 解構。沒有 finally 子句會透過 空值表達式 來表示。

函式

有 3 種方式可以建立匿名函式

scala> val f1 = q"_ + 1"
anon1: universe.Function = ((x$4) => x$4.$plus(1))

scala> val f2 = q"(a => a + 1)"
anon2: universe.Function = ((a) => a.$plus(1))

scala> val f3 = q"(a: Int) => a + 1"
anon3: universe.Function = ((a: Int) => a.$plus(1))

第一種使用佔位符語法。第二種命名函式參數,但仍仰賴型別推論來推論其型別。最後一種明確定義函式參數。由於實作限制,第二種表示法只能用在括號中或其他表達式內。如果您將它們省略,則必須指定參數型別。

參數表示為 Val。如果您想要以程式方式建立一個 val,其型別應該要推論,您需要使用 空值型別

scala> val tpt = tq""
tpt: universe.TypeTree = <type ?>

scala> val param = q"val x: $tpt"
param: universe.ValDef = val x

scala> val fun = q"($param => x)"
fun: universe.Function = ((x) => x)

所有給定的形式都用相同方式表示,並且可以統一比對

scala> List(f1, f2, f3).foreach {
         case q"(..$params) => $body" =>
           println(s"params = $params, body = $body")
       }
params = List(<synthetic> val x$5 = _), body = x$5.$plus(1)
params = List(val a = _), body = a.$plus(1)
params = List(val a: Int = _), body = a.$plus(1)

您也可以進一步拆解引數

scala> val q"(..$params) => $_" = f3
params: List[universe.ValDef] = List(val a: Int = _)

scala> val List(q"$_ val $name: $tpt") = params
name: universe.TermName = a
tpt: universe.Tree = Int

建議您使用底線樣式取代 修飾詞,即使您不打算將它們用作參數,它們可能包含可能會導致比對失敗的其他旗標。

部分函式

部分函式是一種簡潔的語法,讓您可以使用樣式比對來表示具有有限網域的函式

scala> val pf = q"{ case i: Int if i > 0 => i * i }"
pf: universe.Match =
<empty> match {
  case (i @ (_: Int)) if i.$greater(0) => i.$times(i)
}

scala> val q"{ case ..$cases }" = pf
cases: List[universe.CaseDef] = List(case (i @ (_: Int)) if i.$greater(0) => i.$times(i))

樹狀結構中「漂亮列印」檢視的怪異預設,代表它們與配對表達式的樹狀結構共用類似的資料結構。儘管如此,它們並未互相配對

scala> val q”$expr match { case ..$cases }” = pf scala.MatchError: …

While 和 Do-While 迴圈

While 和 do-while 迴圈是低階控制結構,可用於特定反覆運算的效能至關重要時

scala> val `while` = q"while(x > 0) x -= 1"
while: universe.LabelDef =
while$6(){
  if (x.$greater(0))
    {
      x.$minus$eq(1);
      while$6()
    }
  else
    ()
}

scala> val q"while($cond) $body" = `while`
cond: universe.Tree = x.$greater(0)
body: universe.Tree = x.$minus$eq(1)

scala> val `do-while` = q"do x -= 1 while (x > 0)"
do-while: universe.LabelDef =
doWhile$2(){
  x.$minus$eq(1);
  if (x.$greater(0))
    doWhile$2()
  else
    ()
}

scala> val q"do $body while($cond)" = `do-while`
body: universe.Tree = x.$minus$eq(1)
cond: universe.Tree = x.$greater(0)

For 和 For-Yield 迴圈

forfor-yield 表達式允許我們撰寫單子風格的理解,並將其去糖化為對 mapflatMapforeachwithFilter 方法的呼叫

scala> val `for-yield` = q"for (x <- xs; if x > 0; y = x * 2) yield x"
for-yield: universe.Tree =
xs.withFilter(((x) => x.$greater(0))).map(((x) => {
  val y = x.$times(2);
  scala.Tuple2(x, y)
})).map(((x$3) => x$3: @scala.unchecked match {
  case scala.Tuple2((x @ _), (y @ _)) => x
}))

理解中的每個列舉器都可用 fq"..." 內插程式表示

scala> val enums = List(fq"x <- xs", fq"if x > 0", fq"y = x * 2")
enums: List[universe.Tree] = List(`<-`((x @ _), xs), `if`(x.$greater(0)), (y @ _) = x.$times(2))

scala> val `for-yield` = q"for (..$enums) yield y"
for-yield: universe.Tree

類似地,可以將 for-yield 解構回列舉器和主體清單

scala> val q"for (..$enums) yield $body" = `for-yield`
enums: List[universe.Tree] = List(`<-`((x @ _), xs), `if`(x.$greater(0)), (y @ _) = x.$times(2))
body: universe.Tree = x

重要的是提到 forfor-yield 彼此不交叉配對

scala> val q"for (..$enums) $body" = `for-yield`
scala.MatchError: ...

新增

新增表達式讓您建構給定類型的執行個體,可能使用其他類型或定義對其進行精煉

scala> val q"new ..$parents { ..$body }" = q"new Foo(1) with Bar { def baz = 2 }"
parents: List[universe.Tree] = List(Foo(1), Bar)
body: List[universe.Tree] = List(def baz = 2)

請參閱 範本 區段以取得詳細資訊。

匯入

匯入樹狀結構包含一個參考和一個選取器清單

scala> val q"import $ref.{..$sels}" = q"import foo.{bar, baz => boo, poison => _, _}"
ref: universe.Tree = foo
sels: List[universe.Tree] = List((bar @ _), $minus$greater((baz @ _), (boo @ _)), $minus$greater((poison @ _), _), _)

選取器萃取為在語法上類似於選取器的範本樹狀結構

  1. 簡單的識別碼選取器表示為範本繫結:pq"bar"
  2. 重新命名選取器表示為細箭號範本:pq"baz -> boo"
  3. 取消匯入選取器表示為細箭號,其右側為萬用字元:pq"poison -> _"
  4. 萬用字元選取器表示為萬用字元範本:pq"_"

類似地,一個建構從程式化建立的選取器清單中匯入

scala> val ref = q"a.b"
scala> val sels = List(pq"foo -> _", pq"_")
scala> val imp = q"import $ref.{..$sels}"
imp: universe.Import = import a.b.{foo=>_, _}

此頁面的貢獻者