Scala 准引用 - Quasiquote介绍

Quasiquotes are a neat notation that lets you manipulate Scala syntax trees with ease:

scala> val tree = q"i am { a quasiquote }"
tree: universe.Tree = i.am(a.quasiquote)

Every time you wrap a snippet of code in q"..." it will become a tree that represents a given snippet. As you might have already noticed, quotation syntax is just another usage of extensible string interpolation, introduced in 2.10. Although they look like strings they operate on syntactic trees under the hood.

The same syntax can be used to match trees as patterns:

scala> println(tree match { case q"i am { a quasiquote }" => "it worked!" })
it worked!

Whenever you match a tree with a quasiquote it will match whenever the structureof a given tree is equivalent to the one you've provided as a pattern. You can check for structural equality manually with the help of equalsStructure method:

scala> println(q"foo + bar" equalsStructure q"foo.+(bar)")
true

You can also put things into quasiquotation with the help of $:

scala> val aquasiquote = q"a quasiquote"
aquasiquote: universe.Select = a.quasiquote

scala> val tree = q"i am { $aquasiquote }" tree: universe.Tree = i.am(a.quasiquote) 

This operation is also known as unquoting. Whenever you unquote an expression of type Tree in a quasiquote it will structurally substitute that tree into that location. Most of the time such substitutions between quotes is equivalent to a textual substitution of the source code.

Similarly, one can structurally deconstruct a tree using unquoting in pattern matching:

scala> val q"i am $what" = q"i am { a quasiquote }"
what: universe.Tree = a.quasiquote 

Interpolators

Scala is a language with rich syntax that differs greatly depending on the syntactical context:

scala> val x = q"""
         val x: List[Int] = List(1, 2) match {
           case List(a, b) => List(a + b)
         }
       """
x: universe.ValDef =
val x: List[Int] = List(1, 2) match {
  case List((a @ _), (b @ _)) => List(a.$plus(b)) } 

In this example we see three primary contexts being used:

  1. List(1, 2) and List(a + b) are expressions
  2. List[Int] is a type
  3. List(a, b) is a pattern

Each of these contexts is covered by a separate interpolator:

  Used for
q expressions, definitions and imports
tq types
pq patterns

Syntactical similarity between different contexts doesn't imply similarity between underlying trees:

scala> println(q"List[Int]" equalsStructure tq"List[Int]")
false

If we peek under the hood we’ll see that trees are, indeed different:

scala> println(showRaw(q"List[Int]"))
TypeApply(Ident(TermName("List")), List(Ident(TypeName("Int")))) scala> println(showRaw(tq"List[Int]")) AppliedTypeTree(Ident(TypeName("List")), List(Ident(TypeName("Int")))) 

Similarly, patterns and expressions are also not equivalent:

scala> println(pq"List(a, b)" equalsStructure q"List(a, b)")
false

It’s extremely important to use the right interpolator for the job in order to construct a valid syntax tree.

Additionally there are two auxiliary interpolators that let you work with minor areas of scala syntax:

  Used for
cq case clause
fq for loop enumerator

See the section syntax summary for details.

Splicing

Unquote splicing is a way to unquote a variable number of elements:

scala> val ab = List(q"a", q"b")
scala> val fab = q"f(..$ab)" fab: universe.Tree = f(a, b) 

Dots before the unquotee annotate indicate a degree of flattenning and are called a splicing rank..$ expects the argument to be an Iterable[Tree] and ...$ expects an Iterable[Iterable[Tree]].

Splicing can easily be combined with regular unquotation:

scala> val c = q"c"
scala> val fabc = q"f(..$ab, $c)" fabc: universe.Tree = f(a, b, c) scala> val fcab = q"f($c, ..$ab)" fcab: universe.Tree = f(c, a, b) scala> val fabcab = q"f(..$ab, $c, ..$ab)" fabcab: universe.Tree = f(a, b, c, a, b) 

If you want to abstract over applications even further, you can use ...$:

scala> val argss = List(ab, List(c))
arglists: List[List[universe.Ident]] = List(List(a, b), List(c)) scala> val fargss = q"f(...$argss)" fargss: universe.Tree = f(a, b)(c) 

At the moment ...$ splicing is only supported for function applications and parameter lists in def and class definitions.

Similarly to construction one can also use ..$ and ...$ to tear trees apart:

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

There are some limitations in the way you can combine splicing with regular $variable extraction:

case q"f($first, ..$rest)" => // ok case q"f(..$init, $last)" => // ok case q"f(..$a, ..$b)" => // not allowed 

So, in general, only one ..$ is allowed per given list. Similar restrictions also apply to ...$:

case q"f(..$first)(...$rest)" => // ok case q"f(...$init)(..$first)" => // ok case q"f(...$a)(...$b)" => // not allowed 

In this section we only worked with function arguments but the same splicing rules are true for all syntax forms with a variable number of elements. Syntax summaryand the corresponding details sections demonstrate how you can use splicing with other syntactic forms.

你可能感兴趣的:(Scala 准引用 - Quasiquote介绍)