For Development HEAD DRAFTSearch (procedure/syntax/module):

4.9 準クオート(Quasiquote)

Special Form: quasiquote template

[R7RS base] 準クォートは固定部分と変数部分の両方を持つような構造を構成するのに便利 です。詳細は以下の説明を参照してください。

Reader Syntax: `template

[R7RS] `x(quasiquote x)として読み込まれます。

Special Form: unquote datum …
Special Form: unquote-splicing datum …

[R7RS base] これらの構文は準クォートされたtemplate内にあるときにだけ意味を持ち ます。R5RSではこれらの構文が準クォートの外側で現われたときの意味に ついては何も言及していません。Gaucheではそのような場合にはエラーを通知します。 そのようなunquoteunquote-splicingが現れるのは、 通常どこかで準クォートを忘れているからです。

R5RSはunquoteunquote-splicingに一つだけ引数を取ることを 許しています。(unquote)(unquote x y)のようなフォームが 準クオートの中に現れた場合の動作は定義されていません。 R6RSではこれらの場合も定義されており、Gaucheもそれに倣っています。

Reader Syntax: ,datum
Reader Syntax: ,@datum

[R7RS] ,xおよび,@xは、それぞれ(unquote x)および (unquote-splicing x)として読み込まれます。

準クォートの基本

(foo bar x y)のようなリストを構成したいとしましょう。 ここではfooおよびbarはシンボルで、xおよびyは 実行時に定まる値とします。(説明のために、変数xおよびyがそ れらの値を持っているものとします。) ひとつの方法はlist関数を明 示的に呼ぶことです。

(let ((x 0) (y 1))
  (list 'foo 'bar x y)) ⇒ (foo bar 0 1)

同じことを準クォートを使うと以下のようになります。

(let ((x 0) (y 1))
  `(foo bar ,x ,y))  ⇒ (foo bar 0 1)

ふたつの記法の違いは、前者では結果に書いたとおりを入れ込みたいところで クォートを使い、後者ではクォートしたくないところにunquotesを使 うことです。

ほとんどが固定部分でその中に変数部分が散在するような場合には準クォート を使った方が単純で読みやすくなります。

そういうわけで、旧来のマクロでは準クォートが頻繁につかわれていました。 旧来のマクロは基本的にマクロの引数として与えられた変数部分からプログラ ム断片を生成する手続だからです。簡単なmy-ifマクロの定義とそれが condに展開されるようすを見てみましょう。

(define-macro (my-if test then else)
  `(cond (,test ,then)
         (else ,else)))

(macroexpand '(my-if (< n 0) n (- n)))
  ⇒ (cond ((< n 0) n) (else (- n)))

マクロ定義内の2つのelseに注目してください。ひとつはアンクォート されていませんので、出力にはそのまま現われます。もうひとつの方はといえ ば、こちらはアンクォートされていますので、その場所にマクロの引数が入り ます。

もちろんマクロとは関係のないところでも準クォートは使えます。準クォート は構造のあるデータを構築する汎用的な方法です。ほとんどが変数部分である ような構造でも準クォートを好んで使うプログラマもいます。準クォートを使っ た方が簡潔に書けるからです。さらにGaucheでは準クォート形式に対する実行 時アロケーションができるだけ少くなるようにしていますので、準クォートを 使った方が効率がいいはずです。この点については後述の「準クォートはどの くらい静的か」を見てください。

スプライシング

(unquote-splicing expr)が準クォート形式の中で使われていれ ば、exprは評価されてリストになるものでなければならず、それをとり まくコンテキストで継ぎ合わされます。例を見ると簡単にわかります。

(let ((x '(1 2 3)))
  `(a ,@x b)) ⇒ (a 1 2 3 b)

(let ((x '(1 2 3)))
  `(a ,x b)) ⇒ (a (1 2 3) b)

(let ((x '(1 2 3)))
  `#(a ,@x b)) ⇒ #(a 1 2 3 b)

アンクォート版とアンクォートスプライシング版を比べてください。スプライ シングはベクタに対しても機能します。

複数の引数を取るunquote

unquoteunquote-splicingが複数の引数を取る場合は、 あたかも各引数がそれぞれunquoteまたはunquote-splicingされている かのように解釈されます。

;; This is the same result as `(,(+ 1 2) ,(+ 2 3) ,(+ 3 4))
`((unquote (+ 1 2) (+ 2 3) (+ 3 4)))
  ⇒ (3 5 7)

;; This is the same result as
;;   `(,@(list 1 2) ,@(list 2 3) ,@(list 3 4))
`((unquote-splicing (list 1 2) (list 2 3) (list 3 4)))
  ⇒ (1 2 2 3 3 4)

;; Edge cases
`((unquote))          ⇒ ()
`((unquote-splicing)) ⇒ ()

複数のフォームをスプライスできない位置に現れた ゼロ個もしくは複数の引数を取るunquote/unquote-splicingフォームは エラーとなります。

;; Multiple arguments unquotes are error in non-splicing context
`(unquote 1 2)          ⇒ error
`(unquote-splicing 1 2) ⇒ error

省略記法の,x,@xは単一引数のフォームとしてしか使えないので、 ゼロ個もしくは複数の引数を取る場合は unquoteunquote-splicingを陽に表記しなければなりません。 わざわざそんなコーディングをする必要はまず無いでしょう。 この機能は、ネストしたアンクオートしているフォーム、 ,,@,@,@をサポートするためにあります。 R5RSの仕様では、これらのフォームで、内側のunquote-splicingがゼロ個もしくは複数個の フォームへと展開された場合を処理できません。

準クォートはどのくらい静的か

準クォート形式が変数部分を含む場合、実行時には、明示的な形式が使われた ときと同じことがおこります。`(,x ,y)(list x y)のように 評価されます。しかし、Gaucheでは準クォート形式が固定部分を持つ場合には 実行時アロケーションができるだけ少くなるようにします。

まず、`(a b c)のように準クォート形式に変数部分がない場合、全体 は静的にアロケートされます。構造の末尾が固定部分の場合にも静的にアロケー トされます。たとえば、`((,x a b) (,y c d))(list (cons x '(a b)) (cons y '(c d)))のように機能します。

さらにアンクォート式が定数式の場合,Gaucheはそれを準クォートの固定部分 に埋め込みます。たとえば、(define-constant x 3)のように定義した としましょう。この場合`(,x ,(+ x 1))は定数'(3 4)のように コンパイルされます。(define-constant形式については、 定義を参照してください。)

一般的には、準クォート形式のどの部分が固定データとしてコンパイルされど の部分がされないのかを特定することは困難です。それゆえ、準クォートの返 す構造の一部が新規にアロケートされていることを前提としたコードを書いて はいけません。いいかえると、そのような構造を変更するのは避けるべきです。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT