Clojure やっててマクロのあたりの構文クォート、アンクォート、スプライシング・アンクォートあたりが全然分かんなかったので、無理矢理 Groovy っぽく解釈してみました。 サンプルの Groovy コードはかなり力ずくで、(動作はするけど)実際に使えるコードではありませんが、Clojure マクロの理解の助けになれば幸いです。
構文クォート、アンクォート
とりあえず簡単なマクロから。 『プログラミング Clojure』に載っているマクロ「unless」を拝借:
(defmacro unless [expr form] `(if ~expr nil ~form)) ; マクロの本体 (unless false (println "this should print")) ; 使用例1 (unless true (println "this should not print")) ; 使用例2
- unless は最初の引数を評価して false なら次の引数を実行します(if の逆)
- 最初の2行が unless マクロの定義です
- 最後2行は unless の使用例
このコードを実行すると以下のように表示されます:
this should print
これは unless の使用例1の実行によって表示されます。 使用例2は何も表示しません。
さて、マクロを定義する際にややこしいのが構文クォート (syntax-quote `) 、アンクォート (unquote ~)、スプライシング・アンクォート (unquote-splicing ~@)でしょう。 スプライシング・アンクォートは後で見ることにして、まずは最初の2つを見てみましょう。 『プログラミング Clojure』から説明を拝借すると
- 構文クォート
- クォートと似ているが、フォームの一部をアンクォートすることができ、フォームをテンプレートとして使える。
- アンクォート
- 構文クォートの中で使われると、アンクォートのある部分を form で置き換える。
だそうです。 うむ、よく分からん。 とりあえず、上記のサンプルではこの2つを使ってます(構文クォート `(if とアンクォート ~expr, ~form)。
Groovy で似た機能がないかなと考えてみると、GString がなんか使えそうな感じかなぁ、ということで
- 構文クォート (`) → GString ("...")
- アンクォート (~) → GString のプレースホルダー (${...})
という対応で Clojure マクロを Groovy のコードに焼き直してみましょう:
shell = new GroovyShell() def unless(expr, form){ def code = "if (!$expr) { $form }" // マクロの本体に対応 println "// $code" shell.evaluate(code) } unless('false', 'println "this should print"') // 使用例1 unless('true', 'println "this should not print"') // 使用例2
- unless() メソッドの引数は内容が Groovy コードの文字列となってます。 変数やクロージャではありません
- unless() メソッドはマクロに対応する Groovy コードを文字列として作成し(code 変数)、GroovyShell によってコンパイル & 実行しています
- マクロの処理に対応する、生成された Groovy コードも表示されるようにしてます(前に // をつけて表示)
Clojure マクロの本体「`(if ~expr nil ~form)」は GString 「"if (!$expr) { $form }"」となっています*1。 こうやって見ると結構きちんと対応してますな。 ただ、悲しいのが unless メソッドに渡しているのが「内容が Groovy コードの文字列」だってところ。 現実に使えそうにねぇ(笑) このあたり、Clojure の Homoiconicity が光るところでもあるかな(ちょっと専門用語使ってみたw)。
ちなみにサンプルコードを実行すると
// if (!false) { println "this should print" } this should print // if (!true) { println "this should not print" }
と表示されます。 コード確認のための出力(// で始まる)以外は、上記の Clojure マクロと同じ出力が得られます。 ヨシヨシ。
ちょっと修正
ヨシヨシ、うまくいった・・・と思ってたら、早速以下のようなコードが動かない:
x = false unless('x', 'println "this should print"')
まぁ、当然ですが。 これは unless() メソッドの中で GroovyShell によってコンパイル & 実行する際に変数 x にアクセスできないため。 まぁ、あんまり本筋の話とは関係ないので、限定的だけど簡単な修正で済ませましょう:
shell = new GroovyShell(this.binding) // 変数を参照可能に def unless(expr, form){ def code = "if (!$expr) { $form }" println "// $code" shell.evaluate(code) } x = false // 変数を定義(def をつけると動かないヨ) unless('x', 'println "this should print"')
これを実行すると
// if (!x) { println "this should print" } this should print
となって修正完了。
スプライシング・アンクォート
さて、残るはスプライシング・アンクォート。 英語では unquote-splicing らしいけど、何故逆になってんでしょうね? ちなみに splice とは「継ぎ合わせる」という意味だそうで。 『プログラミング Clojure』に載っている説明は
- スプライシング・アンクォート
- 構文クォート中で使われると、form の値であるリストをテンプレートのリストに継ぎ足す形で展開する
ふむふむ、全然分からんw むりやり「継ぎ足す」という語句を使ってるよね(笑) まぁ、とりあえずスプライシング・アンクォートを使った Clojure サンプルを見てみましょう:
(defmacro unless2 [expr & forms] `(if ~expr nil (do ~@forms))) ; マクロ本体 (unless2 false (println "1st line.") (println "2nd line.") (println "3rd line."))
- unless2 は最初の引数の評価結果が false なら、残りの引数を全て実行します(可変長引数)
これを実行すると
1st line. 2nd line. 3rd line.
と表示されます。
スプライシング・アンクォートは、どうも Groovy でいうと GString のプレースホルダーとリスト展開演算子 (*) を合わせたようなものっぽいですな。 いまいちうまいサンプルが書けませんが、以下のような感じです:
shell = new GroovyShell(this.binding) def unless2(expr, String... forms){ def code = "if (!$expr) { ${splice(*forms)} }" // マクロ本体に対応 println "// $code" shell.evaluate(code) } def splice(form){ forms } def splice(form0, form1){ "$form0; $form1" } def splice(form0, form1, form2){ "$form0; $form1; $form2" } //def splice(String... forms){ forms.join('; ') } // でいいんだけどね unless2('false', 'println "1st line."', 'println "2nd line."', 'println "3rd line."')
- splice() メソッドでは引数の文字列(内容が Groovy コード)を ('; ') で連結しています
「マクロの本体に対応」する部分でリスト展開演算子を無理矢理使おうとして、splice() メソッドを3つも定義してますが、実際のところはコメントアウトしている splice() メソッドだけで済みます*2。 上記サンプルを実行すると
// if (!y) { println "1st line."; println "2nd line."; println "3rd line." } 1st line. 2nd line. 3rd line.
と表示されます。 うーむ、ちょっとこのサンプルじゃスプライシング・アンクォートの使い方がイマイチわからんなぁ。 とは言っても、あれこれサンプル書いててちょっとは Clojure のマクロも分かるようになってキタ気がする。
参考

- 作者: Stuart Halloway and Aaron Bedra,川合史朗
- 出版社/メーカー: オーム社
- 発売日: 2013/04/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る

- 作者: Stuart Halloway,川合史朗
- 出版社/メーカー: オーム社
- 発売日: 2010/01/26
- メディア: 単行本(ソフトカバー)
- 購入: 10人 クリック: 338回
- この商品を含むブログ (71件) を見る