2016-01-20 73 views
1

我正在寫一個宏允許通過的條款作爲參數傳遞給函數:clojure.lang.Symbol不能轉換爲java.lang.CharSequence中宏

(defmacro parse-cmd [command & body] 
    (let [parts (str/split command #" ") 
     cmd (first parts) 
     args (into [] (rest parts)) 
     clauses (partition 2 2 body)] 
    `(case ~cmd 
     [email protected](mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args)]) clauses)))) 

(defn mysum [a b] 
    (+ (Integer. a) (Integer. b))) 

(parse-cmd "SET 1 1" "SET" "GET" println) 
2 

這種運作良好,在CMD是串,但是用VAR:

(def cmd "SET 1 1") 
(parse-cmd cmd "SET" "GET" println) 

我得到ClassCastException異常clojure.lang.Symbol不能轉換到java.lang.CharSequenceq clojure.string /分(string.clj:222)

我想我應該防止評估上的讓太多,但我不能讓它工作:

(defmacro parse-cmd [command & body] 
    `(let [parts# (str/split ~command #" ") 
     cmd# (first parts#) 
     args# (into [] (rest parts#)) 
     clauses# (partition 2 2 ~body)] 
    (case cmd# 
     (mapcat (fn [c#] [(nth c# 0) `(apply ~(nth c# 1) args#)]) clauses#)))) 

根據這個定義,我得到: ClassCastException異常java.lang.String中不能轉換到clojure.lang.IFn kvstore.replication/eval12098(外形init7453673077215360561.clj:1)

+0

由於'cmd',你得到'符號不能轉換爲CharSequence'異常,你給出的第一個參數不是字符串。宏不像函數那樣自動評估變量。關於最好的解決方案,你應該考慮一下你應該[macroexpand](http://clojuredocs.org/clojure.core/macroexpand)中的宏是什麼,這將幫助你找到正確的方法。 – schaueho

回答

2

咱們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))) 
相關問題