學習 Clojure/Reader 宏
Reader 宏(不要與普通宏混淆)是一個特殊的字元序列,當 Reader 遇到它時,會修改 Reader 的行為。Reader 宏的存在是為了語法上的簡潔和方便。
'foo ; (quote foo)
#'foo ; (var foo)
@foo ; (clojure.core/deref foo)
#^{:ack bar} foo ; (clojure.core/with-meta foo {:ack bar})
^{:ack bar} foo ; (clojure.core/with-meta foo {:ack bar})
#"regex pattern" ; create a java.util.regex.Pattern from the string (this is done at read time,
; so the evaluator is handed a Pattern, not a form that evaluates into a Pattern)
#(foo %2 bar %) ; (fn [a b] (foo b bar a))
#() 語法用於作為引數傳遞的非常短的函式。它接受名為 %,%2,%3,%n ... %& 的引數。
最複雜的 Reader 宏是 語法引號,用 `(反引號)表示。當它用於符號時,語法引號類似於引號,但符號會解析為它的完全限定名。
`meow ; (quote cat/meow) ...assuming we are in the namespace cat
將語法引號應用於原子值會擴充套件為該值本身。例如
`10 ; expands to 10 `1/2 ; expands to 1/2 `"hello" ; expands to "hello"
當它用於列表、向量或對映形式時,語法引號會引用整個形式,除了 a)所有符號都會解析為它們的完全限定名,以及 b)以 ~ 開頭的元件會被 取消引用
(defn rabbit [] 3) `(moose ~(rabbit)) ; (quote (cat/moose 3)) ...assume namespace cat
(def zebra [1 2 3]) `(moose ~zebra) ; (quote (cat/moose [1 2 3]))
以 ~@ 開頭的元件會被 取消引用拼接
`(moose ~@zebra) ; (quote (cat/moose 1 2 3))
如果一個符號是非名稱空間限定的並且以 '#' 結尾,它會被解析為一個具有相同名稱的生成的符號,其中附加了 '_' 和唯一的 ID。例如 x# 會解析為 x_123。在語法引號表示式中對該符號的所有引用都會解析為相同的生成符號。
`(x#) ; (x__2804__auto__)
對於除符號、列表、向量和對映之外的所有形式,`x 與 'x 相同。
語法引號可以巢狀在其他語法引號中
`(moose ~(squirrel `(whale ~zebra)))
對於列表,語法引號建立了相應資料結構的模板。在模板中,非限定形式的行為就像遞迴語法引號一樣。
`(x1 x2 x3 ... xn)
被解釋為
(clojure.core/seq (clojure.core/concat |x1| |x2| |x3| ... |xn|))
其中 | | 用於指示對 xj 的轉換,如下所示
- |form| 被解釋為 (clojure.core/list `form),它包含一個語法引號形式,然後必須對其進行進一步解釋。
- |~form| 被解釋為 (clojure.core/list form)。
- |~@form| 被解釋為 form。
如果語法引號語法巢狀,則最內部的語法引號形式會首先展開。這意味著,如果在一個行中出現多個 ~,則最左邊的 ~ 屬於最內部的語法引號。
一個重要的例外是空列表
`()
被解釋為
(clojure.core/list)
根據以上規則,並假設 var a 包含 5,一個像這樣的表示式
``(~~a)
會(在幕後)擴充套件為如下所示
(clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/seq))
(clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/concat))
(clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/list))
(clojure.core/list a)))))))))
然後計算,得到;
(clojure.core/seq (clojure.core/concat (clojure.core/list 5)))
當然,相同的表示式也可以等效地擴充套件為
(clojure.core/list `list a)
這實際上更容易閱讀。Clojure 使用前一種演算法,這種演算法在也有拼接的情況下更普遍適用。
其原則是,具有巢狀深度為 k 的語法引號的表示式的結果,只有在執行了 k 次連續計算後才會相同,無論擴充套件演算法如何(Guy Steele)。
對於向量、對映和集合,我們有以下規則
`[x1 x2 x3 ... xn] ; is interpreted as (clojure.core/apply clojure.core/vector `(x1 x2 x3 ... xn))
`{x1 x2 x3 ... xn} ; is interpreted as (clojure.core/apply clojure.core/hash-map `(x1 x2 x3 ... xn))
`#{x1 x2 x3 ... xn} ; is interpreted as (clojure.core/apply clojure.core/hash-set `(x1 x2 x3 ... xn))
如果我們定義語法引號表示式返回什麼,則語法引號最容易理解。要計算語法引號表示式,您需要刪除語法引號和每個匹配的波浪號,並將每個匹配波浪號之後的表示式替換為其值。計算以波浪號開頭的表示式會導致錯誤。
如果在兩個波浪號和語法引號之間有相同數量的波浪號和語法引號,則波浪號與語法引號匹配,其中 b 位於 a 和 c 之間,如果 a 附加到包含 b 的表示式,並且 b 附加到包含 c 的表示式。這意味著在格式良好的表示式中,最外面的語法引號與最裡面的波浪號匹配。
假設 x 計算為 user/a,而 user/a 計算為 1;並且 y 計算為 user/b,而 user/b 計算為 2。
您可以從 REPL 中準備以下內容
user=> (def x `a) user=> (def y `b) user=> (def a 1) user=> (def b 2)
要計算表示式
``(w ~x ~~y )
我們刪除第一個語法引號,並計算任何匹配波浪號之後的表示式。最右邊的波浪號是唯一與第一個語法引號匹配的波浪號。如果我們刪除它並用它的值替換它之字首的表示式 y,我們會得到
`(w ~user/x ~user/b)
注意 x 如何被“解析”為 user/x。在 Clojure 中,任何非限定符號都會被解析!這與 Common Lisp 不同(更好)。
在這個後者的表示式中,兩個波浪號都與語法引號匹配,所以如果我們要依次計算它,我們會得到
(w user/a 2)
波浪號 at(~@)的行為類似於波浪號,除了它之字首的表示式必須同時出現在包含序列中並返回一個列表或序列。然後,返回序列的元素會被拼接回包含序列。所以
``( w ~x ~~@(list `a `b))
計算為
`(w ~user/x ~user/a ~user/b)
目前,Clojure 不允許您定義自己的 Reader 宏,但這可能在未來會改變。