Movatterモバイル変換


[0]ホーム

URL:


Quasiquotes

Expression details

    Language
      This doc page is specific to features shipped in Scala 2, which have either been removed in Scala 3 or replaced by an alternative. Unless otherwise stated, all the code examples in this page assume you are using Scala 2.

      Denys ShabalinEXPERIMENTAL

      Empty

      q"" is used to indicate that some part of the tree is not provided by the user:

      1. Vals,Vars andDefs without the right-hand side have it set toq"".
      2. Abstract type definitions without bounds have them set toq"".
      3. Try expressions without afinally clause have it set toq"".
      4. Case clauses without guards have them set toq"".

      The defaulttoString formatsq"" as<empty>.

      Literal

      Scala has a number of default built-in literals:

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

      All of those values are of typeLiteral except symbols, which have a different representation:

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

      Thanks tolifting, you can also easily create literal trees directly from values of corresponding types:

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

      This would work the same way for all literal types (seestandard liftables exceptNull. Lifting of thenull value into theNull type isn’t supported; useq"null" if you really want to create anull literal:

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

      During deconstruction you can useunlifting to extract values out ofLiteral trees:

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

      Similarly, it would work with all the literal types exceptNull. (seestandard unliftables)

      Identifier and Selection

      Identifiers and member selections are two fundamental primitives that let you refer to other definitions. A combination of two of them is also known as aRefTree.

      Each term identifier is defined by its name and whether it is backquoted:

      scala> val name = TermName("Foo")name: universe.TermName = Fooscala> val foo = q"$name"foo: universe.Ident = Fooscala> val backquoted = q"`$name`"backquoted: universe.Ident = `Foo`

      Although backquoted and non-backquoted identifiers may refer to the same thing they are not syntactically equivalent:

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

      This is because backquoted identifiers have different semantics in pattern patching.

      Apart from matching on identifiers with a given name, you can also extract their name values with the help ofunlifting:

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

      Name ascription is important here because without it you’ll get a pattern that is equivalent to regular pattern variable binding.

      Similarly, you can create and extract member selections:

      scala> val member = TermName("bar")member: universe.TermName = barscala> val q"foo.$name" = selectedname: universe.TermName = bar

      Super and This

      One can usethis andsuper to select precise members within an inheritance chain.

      This tree supports following variations:

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

      So an unqualifiedq"this" is equivalent toq"${tpnme.EMPTY}.this".

      Similarly, forsuper we have:

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

      Application and Type Application

      Value applications and type applications are two fundamental parts from which one can construct calls to Scala functions and methods. Let’s assume that we would like to handle function calls to the following method:

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

      This can be accomplished with the following:

      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"))

      As you can see, we were able to match both calls regardless of whether a specific type application exists. This happens because the type application matcher extracts the empty list of type arguments if the tree is not an actual type application, making it possible to handle both situations uniformly.

      It is recommended to always include type applications when you match on a function with type arguments, as they will be inserted by the compiler during type checking, even if the user didn’t write them explicitly:

      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)

      Other important features of Scala method calls are multiple argument lists and implicit arguments:

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

      Here we might get one, or two subsequent value applications:

      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))

      ...$, in a pattern, allows us to greedily match all subsequent value applications. Similarly to the type arguments matcher, one needs to be careful because it always matches even in the case where no actual value applications exist:

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

      Therefore, it’s recommended to use more specific patterns that check that ensure the extractedargss is not empty.

      Similarly to type arguments, implicit value arguments are automatically inferred during type checking:

      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))

      Assign and Update

      Assign and update are two related ways to explicitly mutate a variable or collection:

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

      As you can see, the update syntax is just syntactic sugar that gets represented as an update method call on given object.

      Nevertheless, quasiquotes let you deconstruct both of them uniformly according to their user-facing syntax:

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

      Wherearray(0) has the same AST as function application.

      On the other hand if you want to treat this two cases separately, it’s possible with the following, more specific pattern:

      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 xupdate array at List(0) with 1

      Return

      Thereturn expression is used to perform an early return from a function.

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

      Throw

      Thethrow expression is used to throw a throwable:

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

      Ascription

      Ascriptions let users annotate the type of intermediate expression:

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

      Annotation

      Expressions can be annotated:

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

      It’s important to mention that such a pattern won’t match if we combine annotation with ascription:

      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

      In this case we need to deconstruct it as anascription and then deconstructtpt as anannotated type.

      Tuple

      Tuples are heteregeneous data structures with built-in user-friendly syntax. The syntax itself is just syntactic sugar that maps ontoscala.TupleN calls:

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

      At the moment, tuples are only supported up to an arity of 22, but this is just an implementation restriction that might be lifted in the future. To find out if a given arity is supported use:

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

      Despite the fact thatTuple1 class exists there is no built-in syntax for it. Single parens around expression do not change its meaning:

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

      It is also common to treatUnit as a nullary tuple:

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

      Quasiquotes also support deconstruction of tuples of arbitrary arity:

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

      This pattern also matches expressions as single-element tuples:

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

      AndUnit as a nullary tuple:

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

      Block

      Blocks are a fundamental primitive used to express a sequence of actions or bindings. Theq"..." interpolator is an equivalent of a block. It allows you to convey more than one expression, separated by a semicolon or a newline:

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

      The only difference betweenq"{...}" andq"..." is how they handle the of case just a single element.q"..." always returns an element itself while a block still remains a block if a single element is not an expression:

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

      Blocks can also be flattened into other blocks with..$:

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

      The same syntax can be used to deconstruct blocks:

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

      Deconstruction always returns the user-defined contents of a block:

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

      Due to automatic flattening of single-element blocks with expressions, expressions themselves are considered to be single-element blocks:

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

      Except for empty tree which is not considered to be a block:

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

      A zero-element block is equivalent to a synthetic unit (one that was inserted by the compiler rather than written by the user):

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

      Such units are used in emptyelse branches ofifs and empty bodies ofcase clauses, making it as convenient to work with those cases as with zero-element blocks.

      If

      There are two varieties of if expressions: those with anelse clause and without it:

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

      A missingelse clause is equivalent to anelse clause that contains a synthetic unit literal (empty block).

      Pattern Match

      Pattern matching is a cornerstone feature of Scala that lets you deconstruct values into their components:

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

      Whereexpr is some non-empty expression and each case is represented with acq"..." quote:

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

      A combination of the two forms allows you to construct and deconstruct arbitrary pattern matches:

      scala> val q"$expr match { case ..$cases }" =           q"foo match { case _: Foo => 'foo case _ => 'notfoo }"expr: universe.Tree = foocases: List[universe.CaseDef] = List(case (_: Foo) => scala.Symbol("foo"), case _ => scala.Symbol("notfoo"))scala> val cq"$pat1 => $body1" :: cq"$pat2 => $body2" :: Nil = casespat1: universe.Tree = (_: Foo)body1: universe.Tree = scala.Symbol("foo")pat2: universe.Tree = _body2: universe.Tree = scala.Symbol("notfoo")

      A case clause without a body is equivalent to one holding a synthetic unit literal (empty block):

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

      The lack of a guard is represented with the help of anempty expression.

      Try

      Atry expression is used to handle possible error conditions and to ensure a consistent state viafinally. Both error handling cases and thefinally clause are optional.

      scala> val q"try $a catch { case ..$b } finally $c" = q"try t"a: universe.Tree = tb: 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 = tb: 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 = tb: List[universe.CaseDef] = List()c: universe.Tree = f

      Similar topattern matching, cases can be further deconstructed withcq"...". The lack of afinally clause is represented with the help of anempty expression.

      Function

      There are three ways to create anonymous function:

      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))

      The first one uses the placeholder syntax. The second one names the function parameter but still relies on type inference to infer its type. An the last one explicitly defines the function parameter. Due to an implementation restriction, the second notation can only be used in parentheses or inside another expression. If you leave them out then you must specify the parameter types.

      Parameters are represented asVals. If you want to programmatically create aval that should have its type inferred you need to use theempty type:

      scala> val tpt = tq""tpt: universe.TypeTree = <type ?>scala> val param = q"val x: $tpt"param: universe.ValDef = val xscala> val fun = q"($param => x)"fun: universe.Function = ((x) => x)

      All of the given forms are represented in the same way and may be matched uniformly:

      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)

      You can also tear arguments apart even further:

      scala> val q"(..$params) => $_" = f3params: List[universe.ValDef] = List(val a: Int = _)scala> val List(q"$_ val $name: $tpt") = paramsname: universe.TermName = atpt: universe.Tree = Int

      It is recommended that you use the underscore pattern in place ofmodifiers, even if you don’t plan to work with them as parameters, they may contain additional flags which might cause match failures.

      Partial Function

      Partial functions are a neat syntax that let you express functions with a limited domain by using pattern matching:

      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 }" = pfcases: List[universe.CaseDef] = List(case (i @ (_: Int)) if i.$greater(0) => i.$times(i))

      A weird default for the “pretty printed” view on the tree represents the fact that they share a similar data structure as do trees for match expressions. Despite this fact, they do not match one another:

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

      While and Do-While Loops

      While and do-while loops are low-level control structures that can be used when performance of a particular iteration is critical:

      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 and For-Yield Loops

      for andfor-yield expressions allow us to write a monadic style comprehension that desugar into calls tomap,flatMap,foreach andwithFilter methods:

      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}))

      Each enumerator in the comprehension can be expressed with thefq"..." interpolator:

      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

      Similarly, one can deconstruct thefor-yield back into a list of enumerators and body:

      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

      It’s important to mention thatfor andfor-yield do not cross-match each other:

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

      New

      New expressions let you construct an instance of given type, possibly refining it with other types or definitions:

      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)

      See thetemplates section for details.

      Import

      Import trees consist of a reference and a list of selectors:

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

      Selectors are extracted as pattern trees that are syntactically similar to selectors:

      1. Simple identifier selectors are represented as pattern bindings:pq"bar"
      2. Renaming selectors are represented as thin arrow patterns:pq"baz -> boo"
      3. Unimport selectors are represented as thin arrows with a wildcard right-hand side:pq"poison -> _"
      4. The wildcard selector is represented as a wildcard pattern:pq"_"

      Similarly, one construct imports back from a programmatically created list of selectors:

      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=>_, _}

      Contributors to this page:

      Contents


      [8]ページ先頭

      ©2009-2025 Movatter.jp