咱們macroexpand這個(你的第二個宏)
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
它擴展爲:
(let [parts__31433__auto__ (str/split "SET 1 1" #" ")
cmd__31434__auto__ (first parts__31433__auto__)
args__31435__auto__ (into [] (rest parts__31433__auto__))
clauses__31436__auto__ (partition
2
2
("SET" mysum "GET" println))]
(case
cmd__31434__auto__
(mapcat
(fn [c__31437__auto__] [(nth c__31437__auto__ 0)
(seq
(concat
(list 'apply)
(list (nth c__31437__auto__ 1))
(list 'args__31432__auto__)))])
clauses__31436__auto__)))
有兩個問題在這裏:
1)你生成這個代碼:("SET" mysum "GET" println)
,這顯然會導致你的例外,因爲「SET」不
2)你產生了錯誤的case
表達的功能,我看到你已經忘記解除引用,拼接你mapcat
讓我們嘗試解決這個問題:
首先解除引用mapcat
;然後你可以移動clauses
出你產生放過,因爲它可以在編譯時完全做到:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))]
(case cmd#
[email protected](mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) args#)]) clauses)))))
現在讓我們來看看這些擴展:
(let [parts__31653__auto__ (str/split "SET 1 1" #" ")
cmd__31654__auto__ (first parts__31653__auto__)
args__31655__auto__ (into [] (rest parts__31653__auto__))]
(case
cmd__31654__auto__
"SET"
(apply mysum args__31652__auto__)
"GET"
(apply println args__31652__auto__)))
確定。看起來更好。讓我們試着運行它:
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
我們現在有另一個錯誤:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: args__31652__auto__ in this context, compiling:(*cider-repl ttask*:2893:12)
所以擴張也爲我們展示了這一點:
args__31655__auto__ (into [] (rest parts__31653__auto__))
...
(apply mysum args__31652__auto__)
所以有args#
這裏不同的符號。這是因爲生成的符號名稱的範圍是一個語法引用。所以內部語法引用apply
會生成新的語法。您應該使用gensym
來解決這個問題:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
~args-sym (into [] (rest parts#))]
(case cmd#
[email protected](mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args-sym)]) clauses)))))
確定現在應該正常工作:
ttask.core> (parse-cmd "SET 1 1" "SET" mysum "GET" println)
2
ttask.core> (parse-cmd cmd "SET" mysum "GET" println)
2
太棒了!
我也建議你在mapcat
功能,並引述讓用解構,以使其更易於閱讀:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [[cmd# & ~args-sym] (str/split ~command #" ")]
(case cmd#
[email protected](mapcat (fn [[op fun]] [op `(apply ~fun ~args-sym)]) clauses)))))
但是,如果它不只是在編寫宏練習,你不應該使用宏,因爲你在這裏只傳遞字符串和函數引用,所以無論如何你應該在運行時評估所有的東西。
(defn parse-cmd-1 [command & body]
(let [[cmd & args] (str/split command #" ")
commands-map (apply hash-map body)]
(apply (commands-map cmd) args)))
由於'cmd',你得到'符號不能轉換爲CharSequence'異常,你給出的第一個參數不是字符串。宏不像函數那樣自動評估變量。關於最好的解決方案,你應該考慮一下你應該[macroexpand](http://clojuredocs.org/clojure.core/macroexpand)中的宏是什麼,這將幫助你找到正確的方法。 – schaueho