无法在宏中将clojure.lang.Symbol强制转换为java.lang.CharSequence

无法在宏中将clojure.lang.Symbol强制转换为java.lang.CharSequence,clojure,macros,Clojure,Macros,我正在编写一个宏,允许将子句作为参数传递给函数: (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]

我正在编写一个宏,允许将子句作为参数传递给函数:

(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是一个字符串时,这很好地工作,但是使用变量:

(def cmd "SET 1 1")
(parse-cmd cmd "SET"  "GET" println)
I get 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)

让我们对其进行宏扩展(用于第二个宏)

它扩展到:

(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
;然后您可以将
子句
移出生成的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)))
让我们展开这个宏(对于第二个宏)

它扩展到:

(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
;然后您可以将
子句
移出生成的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)))

您得到的
符号不能转换为CharSequence
异常是因为
cmd
,您给出的第一个参数不是字符串。宏不会像函数那样自动计算变量。关于最佳解决方案,您应该考虑宏应该包含哪些内容,这将帮助您找到正确的方法。您得到的
符号不能转换为CharSequence
异常,因为
cmd
,您给出的第一个参数不是字符串。宏不会像函数那样自动计算变量。关于最好的解决方案,你应该考虑一下你要做什么,这将帮助你找到正确的方法。