在使用params的clojure宏中使用匿名函数

时间:2014-06-15 09:05:59

标签: macros clojure

mydata是关于使用的名称和性别:

(def mydata [["a" 'f] ["b" 'm]])

我想要的是:

(group-by #(let [[name gender] %1] name) mydata)
; {"a" [["a" f]], "b" [["b" m]]}

还有:

(group-by #(let [[name gender] %1] gender) mydata)
; {f [["a" f]], m [["b" m]]}

所以我想构建一个这样的函数:

(defn my-group-by [data, field]
  (group-by #(let [[name gender] %1] field) mydata))

但它出错了

(mygroup-by mydata 'name)
; {name [["a" :F] ["b" :M]]}

然后我认为宏可以做到这一点

(defmacro my-macro [data field]
  `(group-by #(let [[name,gender] %1] ~field) ~data))

然后我运行它

(my-macro mydata name)
; CompilerException java.lang.RuntimeException: Can't let qualified name: clojure.core/name, compiling:(/tmp/form-init2648255959159095748.clj:1:1) 

为什么呢?我哪里错了?

3 个答案:

答案 0 :(得分:6)

没有必要为此使用宏。

如果您将数据表示为一系列地图:

(def mydata [{:name "a", :gender :F}
             {:name "b", :gender :M}
             {:name "a", :gender :M}]) ; extra line

...然后你可以使用地图的关键字作为函数:

(group-by :gender mydata)
{:F [{:gender :F, :name "a"}],
 :M [{:gender :M, :name "b"} {:gender :M, :name "a"}]}

(group-by :name mydata)
{"a" [{:gender :F, :name "a"} {:gender :M, :name "a"}],
 "b" [{:gender :M, :name "b"}]}

如果地图证明太慢,您可以将它们变成记录:

(defrecord user [name gender])

(def mydata [(map->user {:name "a", :gender :F})
             (map->user {:name "b", :gender :M})
             (map->user {:name "a", :gender :M})])

......像地图一样工作。

答案 1 :(得分:4)

使用宏在这里有点矫枉过正。 使用地图并按其键排序更为常见。 如果您确实需要使用自己的矢量结构进行排序,可以采用以下方法:

(defn my-group-by [data field] 
  (group-by 
    #(nth % (.indexOf ['name 'gender] field))
    data))

然后,像

一样使用它
(my-group-by mydata 'name)
(my-group-by mydata 'gender)

然而,我个人会将所有出现的'name with name'更改为'','sex with having“性别”。

答案 2 :(得分:0)

对于宏错误:

在宏中你必须使用#来修改局部变量以避免作为宏的参数传递的值的范围问题...你不能从宏中引用宏内部定义的变量...和这正是你想要做的。宏参数是值,它不是C宏中的重写过程。

(defmacro my-macro [data field]
  (group-by #(let [[name# gender#] %1] ~field) ~data))

您必须将名称#或性别#传递给宏,但它没有任何意义,因为它没有在宏外定义...所以Thumbnail给出的解决方案是正确的。

但是你可以这样做(仅仅是因为它不是一个正确的解决方案):

(defmacro my-macro-name [data]
  `(group-by #(let [[name# _] %1] name#) ~data))

但在这种情况下,alfredx写道显然更容易,但我更喜欢使用关键字:

(defn my-fn [data field]
  (group-by #(nth % (.indexOf [:name :gender] field)) data))