|
9 | 9 |
|
10 | 10 | title: 準クォート |
11 | 11 | --- |
12 | | -<a href="/ja/overviews/macros/paradise.html"><span class="label important" style="float: right;">MACRO PARADISE</span></a> |
13 | 12 |
|
14 | | -**Eugene Burmako 著**<br> |
15 | | -**Eugene Yokota 訳** |
16 | | - |
17 | | -準クォート (quasiquote) は 2.11.0-M4 以降の Scala 2.11 のマイルストーン版から含まれる機能だ。Scala 2.10 からもマクロパラダイスプラグインを使うことによって利用可能だ。詳細は[マクロパラダイス](/ja/overviews/macros/paradise.html)ページの説明にしたがってコンパイラプラグインをダウンロードしてほしい。 |
18 | | - |
19 | | -2.10.x と 2.11 の両方において、準クォートはマクロパラダイスに対するライブラリ依存性を導入しないことに注意してほしい。 |
20 | | -そのため、2.10.x においてマクロパラダイスから準クォートを用いてマクロを書いても、そのマクロを使うユーザは素の 2.10.x から使うことができる。 |
21 | | -マクロパラダイスから準クォートを使うコードも、そのコードを使うユーザも実行時にマクロパラダイスをクラスパスに含める必要はない。 |
22 | | - |
23 | | -## 直観 |
24 | | - |
25 | | -例として、あるクラスかオブジェクトを受け取り、その全てのメソッドを `future` でラッピングした非同期版の複製を作る `async` という[マクロアノテーション](/ja/overviews/macros/annotations.html)を考える。 |
26 | | - |
27 | | - @async |
28 | | - class D { |
29 | | - def x = 2 |
30 | | - // def asyncX = future { 2 } |
31 | | - } |
32 | | - |
33 | | - val d = new D |
34 | | - d.asyncX onComplete { |
35 | | - case Success(x) => println(x) |
36 | | - case Failure(_) => println("failed") |
37 | | - } |
38 | | - |
39 | | -そのようなマクロの実装は以下のコードの抜粋のようにできる。この取得、分解、生成コードでラッピング、再構築という流れはマクロ作者にとっては見慣れたものだ。 |
40 | | - |
41 | | - case ClassDef(_, _, _, Template(_, _, defs)) => |
42 | | - val defs1 = defs collect { |
43 | | - case DefDef(mods, name, tparams, vparamss, tpt, body) => |
44 | | - val tpt1 = if (tpt.isEmpty) tpt else AppliedTypeTree( |
45 | | - Ident(newTermName("Future")), List(tpt)) |
46 | | - val body1 = Apply( |
47 | | - Ident(newTermName("future")), List(body)) |
48 | | - val name1 = newTermName("async" + name.capitalize) |
49 | | - DefDef(mods, name1, tparams, vparamss, tpt1, body1) |
50 | | - } |
51 | | - Template(Nil, emptyValDef, defs ::: defs1) |
52 | | - |
53 | | -しかし、ベテランのマクロ作者でもこのコードは、かなりシンプルであることは確かだが、例えば、`AppliedTypeTree` と `Apply` の違いなどコードの内部表現の詳細に理解していることを必要とした必要以上に冗長なものであることを認めるだろう。準クォートはパラメータ化された Scala のコードを Scala を使って表現できるドメイン特化言語を提供する: |
54 | | - |
55 | | - val q"class $name extends Liftable { ..$body }" = tree |
56 | | - |
57 | | - val newdefs = body collect { |
58 | | - case q"def $name[..$tparams](...$vparamss): $tpt = $body" => |
59 | | - val tpt1 = if (tpt.isEmpty) tpt else tq"Future[$tpt]" |
60 | | - val name1 = newTermName("async" + name.capitalize) |
61 | | - q"def $name1[..$tparams](...$vparamss): $tpt1 = future { $body }" |
62 | | - } |
63 | | - |
64 | | - q"class $name extends AnyRef { ..${body ++ newdefs} }" |
65 | | - |
66 | | -現行の準クォートは [SI-6842](https://issues.scala-lang.org/browse/SI-6842) のため、上記のようには簡潔に書くことができない。[多くのキャスト](https://gist.github.com/7ab617d054f28d68901b)を適用して使えるようになる。 |
67 | | - |
68 | | -## 詳細 |
69 | | - |
70 | | -準クォートは `scala.reflect.api.Universe` cake の一部として実装されているため、マクロから準クォートを使うには `import c.universe._` とするだけでいい。公開されている API は `q`、`tq`、`cq`、そして `pq` [文字列補間子](/ja/overviews/core/string-interpolation.html)を提供し (値と型の準クォートに対応する)、構築と分解の両方をサポートする。つまり、普通のコードとパターンケースの左辺値において使うことができる。 |
71 | | - |
72 | | -<table> |
73 | | -<thead> |
74 | | -<tr><th>補間子</th><th>対象</th><th>構築</th><th>分解</th></tr> |
75 | | -</thead> |
76 | | -<tbody> |
77 | | -<tr><td><code>q</code></td><td>値構文木</td><td><code>q"future{ $body }"</code></td><td><code>case q"future{ $body }" =></code></td></tr> |
78 | | -<tr><td><code>tq</code></td><td>型構文木</td><td><code>tq"Future[$t]"</code></td><td><code>case tq"Future[$t]" =></code></td></tr> |
79 | | -<tr><td><code>cq</code></td><td>ケース</td><td><code>cq"x => x"</code></td><td><code>case cq"$pat => ${_}" =></code></td></tr> |
80 | | -<tr><td><code>pq</code></td><td>パターン</td><td><code>pq"xs @ (hd :: tl)"</code></td><td><code>case pq"$id @ ${_}" =></code></td></tr> |
81 | | -</tbody> |
82 | | -</table> |
83 | | - |
84 | | -普通の文字列補間子と違い、準クォートは単独の構文木、構文木のリスト、構文木のリストのリストの挿入または抽出を区別するために複数のスプライシングの方法をサポートしている。スプライス対象とスプライス演算子の基数のミスマッチはコンパイル時のエラーとなる。 |
85 | | - |
86 | | - scala> val name = TypeName("C") |
87 | | - name: reflect.runtime.universe.TypeName = C |
88 | | - |
89 | | - scala> val q"class $name1" = q"class $name" |
90 | | - name1: reflect.runtime.universe.Name = C |
91 | | - |
92 | | - scala> val args = List(Literal(Constant(2))) |
93 | | - args: List[reflect.runtime.universe.Literal] = List(2) |
94 | | - |
95 | | - scala> val q"foo(..$args1)" = q"foo(..$args)" |
96 | | - args1: List[reflect.runtime.universe.Tree] = List(2) |
97 | | - |
98 | | - scala> val argss = List(List(Literal(Constant(2))), List(Literal(Constant(3)))) |
99 | | - argss: List[List[reflect.runtime.universe.Literal]] = List(List(2), List(3)) |
100 | | - |
101 | | - scala> val q"foo(...$argss1)" = q"foo(...$argss)" |
102 | | - argss1: List[List[reflect.runtime.universe.Tree]] = List(List(2), List(3)) |
103 | | - |
104 | | -## コツとトリック |
105 | | - |
106 | | -### Liftable |
107 | | - |
108 | | -非構文木のスプライシングを簡易化するために、準クォートは `Lifttable` 型クラスを提供して、値がスプライスされたときにどのように構文木に変換されるかを定義する。プリミティブ型と文字列を `Literal(Constant(...))` にラッピングする `Liftable` インスタンスは提供されている。簡単なケースクラスやリストのための独自のインスタンスを定義することをお勧めする。 |
109 | | - |
110 | | - trait Liftable[T] { |
111 | | - def apply(universe: api.Universe, value: T): universe.Tree |
112 | | - } |
| 13 | +準クォートのガイドは [/overviews/quasiquotes/intro.html](/overviews/quasiquotes/intro.html) に移動した。 |
0 commit comments