此文件頁面特定於 Scala 2 中發布的功能,這些功能已在 Scala 3 中移除或由其他功能取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用的是 Scala 2。
Denys Shabalin 實驗性
空值
q""
用於表示樹的某些部分並非由使用者提供
- 沒有右邊的
Val
、Var
和Def
會將其設定為q""
。 - 沒有界限的抽象類型定義會將其設定為
q""
。 - 沒有
finally
子句的Try
表達式會將其設定為q""
。 - 沒有防護的
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
人們可以使用 this
和 super
在繼承鏈中選擇精確的成員。
此樹支援下列變異
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 = ()
此類單位用於 if 的 else
分支,以及 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 迴圈
for
和 for-yield
表達式允許我們撰寫單子風格的理解,並將其去糖化為對 map
、flatMap
、foreach
和 withFilter
方法的呼叫
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
重要的是提到 for
和 for-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 @ _), _), _)
選取器萃取為在語法上類似於選取器的範本樹狀結構
- 簡單的識別碼選取器表示為範本繫結:
pq"bar"
- 重新命名選取器表示為細箭號範本:
pq"baz -> boo"
- 取消匯入選取器表示為細箭號,其右側為萬用字元:
pq"poison -> _"
- 萬用字元選取器表示為萬用字元範本:
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=>_, _}