对于我有时使用的宏,还有一个更深奥的用例:编写简洁易读的代码,这些代码也经过全面优化。这是一个简单的例子:
(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.")
另一个不那么琐碎的例子:
(defmacro jprint [& xs] `(doto *out* ~@(for [x xs] `(.append ~x))))
这&
意味着它接受可变数量的参数(可变参数、可变参数函数)。在 Clojure 中,可变参数函数调用利用堆分配的集合来传输参数(就像在 Java 中使用数组一样)。这不是很理想,但如果我使用像上面这样的宏,那么就没有函数调用。我这样使用它:
(jprint \" (json-escape item) \")
它编译成三个调用PrintWriter.append
(基本上是一个展开的循环)。
最后,我想向你们展示一些更完全不同的东西。您可以使用宏来帮助您定义类似函数的类,从而消除大量样板文件。举这个熟悉的例子:在 HTTP 客户端库中,我们希望每个 HTTP 方法都有一个单独的函数。每个函数定义都非常复杂,因为它有四个重载签名。此外,每个函数都涉及来自 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)))