如何将参数列表作为向量传递给Clojure中的协议声明?

时间:2015-01-26 00:40:03

标签: parameters clojure protocols

当我在REPL上输入时:

> (defprotocol protocolname (methodname [a b]))

这很有效。

protocolname

同样当我在REPL上输入它时:

> (defprotocol protocolname (methodname ['a 'b]))

有效。

protocolname

但是当我在REPL上输入它时:

> (let [arg-vec ['a 'b]]
    (defprotocol protocolname (methodname arg-vec)))

失败并出现此错误:

CompilerException java.lang.IllegalArgumentException: Parameter declaration missing,

我的问题是: 为什么这个Clojure协议声明会因java.lang.IllegalArgumentException: Parameter declaration missing意外失败?


更新 - 感谢@Mars - 我们还提供了一些其他信息:

线的宏展开

(pprint (macroexpand-1 '(defprotocol protocolname (methodname [a b]))))

包含以下行:

:sigs
 '{:methodname {:doc nil, :arglists ([a b]), :name methodname}}

该行的宏观扩展:

(let [arg-vec ['a 'b]]
   (pprint (macroexpand-1
             '(defprotocol protocolname (methodname arg-vec)))))

有这条线:

'{:methodname {:doc arg-vec, :arglists nil, :name methodname}}

这告诉我们,当您将vector var传递给defprotocol声明时 - var将分配给:doc而不是:arglist。 (但是当您在dedprotocol调用中声明向量时,它会将向量分配给:arglist

所以我要更新这个问题。

现在的问题是:如何将参数列表作为Clojure中协议声明的向量传递?

2 个答案:

答案 0 :(得分:1)

其他人将能够给出更深层次的答案,但以下的永无止境似乎值得提供答案:

如果你执行

(pprint (macroexpand-1 '(defprotocol protocolname (methodname ['a 'b]))))

(pprint (macroexpand-1 '(defprotocol protocolname (methodname [a b]))))

然后将它们的输出与

的输出进行比较
(let [arg-vec ['a 'b]]
   (pprint (macroexpand-1
             '(defprotocol protocolname (methodname arg-vec)))))

你会发现宏defprotocol的扩展方式存在显着差异。 (我不会在这里重现扩展,因为前两个很长。)

宏展开过程会查看实际的预评估参数。快速浏览一下扩展显示,当methodname之后的表单是一个序列时,每个序列的元素都是单独处理的,而当表单是arg-vec时,它只是arg-vec,没有评估,由扩展过程处理。

答案 1 :(得分:0)

有工作解决方案,但它并不好(因为eval使用):

(defmacro defprotocol-with-args [name methodname args-coll]
  `(defprotocol ~name (~methodname [~@(eval args-coll)])))

您可以将[~@(eval args-coll)]更改为~(eval args-coll),但之后它只能用于向量(上面的代码适用于任何平符号序列)。

试验:

(defprotocol-with-args protocolname methodname '[a b]) ; => protocolname
(def args '[a b]) ; => #'user/args
(defprotocol-with-args protocolname methodname args) ; => protocolname
(macroexpand-1 `(defprotocol-with-args protocolname methodname args)) ; => (clojure.core/defprotocol user/protocolname (user/methodname [a b]))
(macroexpand-1 `(defprotocol-with-args protocolname methodname '[a b])) ; => (clojure.core/defprotocol user/protocolname (user/methodname [user/a user/b]))

注意仅在REPL中测试。在一般情况下,很难预测这种方法的表现如何。