私が時々使用するマクロのより難解なユースケースもあります。完全に最適化された簡潔で読みやすいコードを書くことです。簡単な例を次に示します。
(defmacro str* [& ss] (apply str (map eval ss)))
これは、コンパイル時に文字列を連結します(もちろん、コンパイル時定数である必要があります)。Clojureの通常の文字列連結関数はstr
、タイトループコードのどこにでも長い文字列があり、いくつかの文字列リテラルに分割したい場合は、スターを追加してstr
実行時の連結をコンパイル時に変更するだけです。使用法:
(str* "I want to write some very lenghty string, most often it will be a complex"
" SQL query. I'd hate if it meant allocating the string all over every time"
" this is executed.")
もう1つの、ささいな例:
(defmacro jprint [& xs] `(doto *out* ~@(for [x xs] `(.append ~x))))
可変数の&
引数(varargs、可変個引数関数)を受け入れることを意味します。Clojureでは、可変個引数関数呼び出しは、ヒープに割り当てられたコレクションを使用して引数を転送します(Javaのように、配列を使用します)。これはあまり最適ではありませんが、上記のようなマクロを使用すると、関数呼び出しはありません。私はそれを次のように使用します:
(jprint \" (json-escape item) \")
PrintWriter.append
これは、 (基本的に展開されたループ)の3つの呼び出しにコンパイルされます。
最後に、さらに根本的に異なるものをお見せしたいと思います。マクロを使用して、同様の機能のクラスを定義し、大量の定型文を排除するのに役立てることができます。このおなじみの例を見てください。HTTPクライアントライブラリでは、HTTPメソッドごとに個別の関数が必要です。4つのオーバーロードされたシグニチャがあるため、すべての関数定義は非常に複雑です。また、各関数にはApache HttpClientライブラリからの異なるリクエストクラスが含まれますが、それ以外はすべてすべてのHTTPメソッドでまったく同じです。これを処理するために必要なコードの量を見てください。
(defmacro- def-http-method [name]
`(defn ~name
([~'url ~'headers ~'opts ~'body]
(handle (~(symbol (str "Http" (s/capitalize name) ".")) ~'url) ~'headers ~'opts ~'body))
([~'url ~'headers ~'opts] (~name ~'url ~'headers ~'opts nil))
([~'url ~'headers] (~name ~'url ~'headers nil nil))
([~'url] (~name ~'url nil nil nil))))
(doseq [m ['GET 'POST 'PUT 'DELETE 'OPTIONS 'HEAD]]
(eval `(def-http-method ~m)))