我正在写一个宏以允许将子句作为参数传递给函数:
(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))))
(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 / split(string.clj:222)
我想我也应该阻止对let的评价,但我无法让它发挥作用:
(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(form-init7453673077215360561.clj:1)
答案 0 :(得分:3)
让我们宏展开这个(对于你的第二个宏)
(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
移出生成的let,因为它可以在编译时完成:
(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#
~@(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#
~@(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
函数中使用destructuring并引用let,以使其更具可读性:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [[cmd# & ~args-sym] (str/split ~command #" ")]
(case cmd#
~@(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)))