Y分鐘學clojure宏

和全部Lisp同樣,Clojure內在的同構性使得你能夠窮盡語言的特性,編寫生成代碼的子過程——「宏」。宏是一種按需調製語言的強大方式。html

當心!能夠用函數完成的事用宏去實現可不是什麼好事。你應該僅在須要控制參數是否或者什麼時候eval的時候使用宏。segmentfault

你應該熟悉Clojure.確保你瞭解Y分鐘學Clojure中的全部內容。app

使用defmacro定義宏。宏應該輸出一個能夠做爲clojure代碼演算的列表。less

如下宏的效果和直接寫(reverse "Hello World")一致。函數

(defmacro my-first-macro []
  (list reverse "Hello World"))

使用macroexpand或macroexpand-1查看宏的結果。測試

注意,調用須要引用。spa

(macroexpand '(my-first-macro))
;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")

你能夠直接eval macroexpand的結果code

(eval (macroexpand '(my-first-macro)))
; -> (\d \l \o \r \W \space \o \l \l \e \H)

不過通常使用如下形式,更簡短,更像函數:orm

(my-first-macro)  ; -> (\d \l \o \r \W \space \o \l \l \e \H)

建立宏的時候可使用更簡短的引用形式來建立列表htm

(defmacro my-first-quoted-macro []
  '(reverse "Hello World"))

(macroexpand '(my-first-quoted-macro))
;; -> (reverse "Hello World")

注意reverse再也不是一個函數對象,而是一個符號。

宏能夠傳入參數。

(defmacro inc2 [arg]
  (list + 2 arg))

(inc2 2) ; -> 4

不過,若是你嘗試配合使用引用列表,會致使錯誤,
由於參數也會被引用。
爲了不這個問題,clojure提供了引用宏的另外一種方式:`
在`以內,你可使用~得到外圈做用域的變量。

(defmacro inc2-quoted [arg]
  `(+ 2 ~arg))

(inc2-quoted 2)

你可使用一般的析構參數。用~@展開列表中的變量。

(defmacro unless [arg & body]
  `(if (not ~arg)
     (do ~@body))) ; 別忘了 do!

(macroexpand '(unless true (reverse "Hello World")))

;; ->
;; (if (clojure.core/not true) (do (reverse "Hello World")))

當第一個參數爲假時,(unless)會演算、返回主體。
不然返回nil。

(unless true "Hello") ; -> nil
(unless false "Hello") ; -> "Hello"

須要當心,宏會搞亂你的變量

(defmacro define-x []
  '(do
     (def x 2)
     (list x)))

(def x 4)
(define-x) ; -> (2)
(list x) ; -> (2)

使用gensym來得到獨有的標識符

(gensym 'x) ; -> x1281 (or some such thing)

(defmacro define-x-safely []
  (let [sym (gensym 'x)]
    `(do
       (def ~sym 2)
       (list ~sym))))

(def x 4)
(define-x-safely) ; -> (2)
(list x) ; -> (4)

你能夠在 ` 中使用 # 爲每一個符號自動生成gensym

(defmacro define-x-hygenically []
  `(do
     (def x# 2)
     (list x#)))

(def x 4)
(define-x-hygenically) ; -> (2)
(list x) ; -> (4)

一般會配合宏使用幫助函數。
讓咱們建立一些幫助函數來支持(無聊的)算術語法:

(declare inline-2-helper)
(defn clean-arg [arg]
  (if (seq? arg)
    (inline-2-helper arg)
    arg))

(defn apply-arg
  "Given args [x (+ y)], return (+ x y)"
  [val [op arg]]
  (list op val (clean-arg arg)))

(defn inline-2-helper
  [[arg1 & ops-and-args]]
  (let [ops (partition 2 ops-and-args)]
    (reduce apply-arg (clean-arg arg1) ops)))

在建立宏前,咱們能夠先測試

(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))

然而,若是咱們但願它在編譯期執行,就須要建立宏

(defmacro inline-2 [form]
  (inline-2-helper form)))

(macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1)))
; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1)

(inline-2 (1 + (3 / 2) - (1 / 2) + 1))
; -> 3 (事實上,結果是3N, 由於數字被轉化爲帶/的有理分數)

擴展閱讀

Clojure for the Brave and True系列的編寫宏
http://www.braveclojure.com/writing-macros/

官方文檔
http://clojure.org/macros

什麼時候使用宏?
http://dunsmor.com/lisp/onlisp/onlisp_12.html

相關文章
相關標籤/搜索