Reputation: 289
mydata is about use`s name and gender:
(def mydata [["a" 'f] ["b" 'm]])
what I want is:
(group-by #(let [[name gender] %1] name) mydata)
; {"a" [["a" f]], "b" [["b" m]]}
and also:
(group-by #(let [[name gender] %1] gender) mydata)
; {f [["a" f]], m [["b" m]]}
so I want build a function like this:
(defn my-group-by [data, field]
(group-by #(let [[name gender] %1] field) mydata))
but it go wrong
(mygroup-by mydata 'name)
; {name [["a" :F] ["b" :M]]}
and then I thought macro could do that
(defmacro my-macro [data field]
`(group-by #(let [[name,gender] %1] ~field) ~data))
then I run it
(my-macro mydata name)
; CompilerException java.lang.RuntimeException: Can't let qualified name: clojure.core/name, compiling:(/tmp/form-init2648255959159095748.clj:1:1)
why? Where I`m wrong?
Upvotes: 1
Views: 590
Reputation: 818
For the macro error :
In a macro you have to postfix local variables with # to avoid scope problem with values passed as arguments to the macro... You cannot refer from out of the macro to a variable that is defined inside the macro... and that was exactly what you wanted to do. Macro arguments are values, it's not a rewriting process as in C macros.
(defmacro my-macro [data field]
(group-by #(let [[name# gender#] %1] ~field) ~data))
You would have to pass name# or gender# to the macro but it has no sense as it's not defined outside the macro... So the solution given by Thumbnail is the correct one.
But you can do (just for info as it's not a correct solution):
(defmacro my-macro-name [data]
`(group-by #(let [[name# _] %1] name#) ~data))
But in this case it's obviously easier to do as alfredx wrote, but I would prefer to use keywords :
(defn my-fn [data field]
(group-by #(nth % (.indexOf [:name :gender] field)) data))
Upvotes: 1
Reputation: 1788
Using macro is a bit overkill here. Using map and sort by its key is more common. If you do need to sort using your own vector structure, here is one way to do it:
(defn my-group-by [data field]
(group-by
#(nth % (.indexOf ['name 'gender] field))
data))
Then, using it like
(my-group-by mydata 'name)
(my-group-by mydata 'gender)
Yet I would personally replace all occurrences of 'name with "name", 'gender with "gender".
Upvotes: 4
Reputation: 13473
There is no need to use macros for this.
If you represent your data as a sequence of maps:
(def mydata [{:name "a", :gender :F}
{:name "b", :gender :M}
{:name "a", :gender :M}]) ; extra line
... then you can use the keywords of the maps as functions:
(group-by :gender mydata)
{:F [{:gender :F, :name "a"}],
:M [{:gender :M, :name "b"} {:gender :M, :name "a"}]}
and
(group-by :name mydata)
{"a" [{:gender :F, :name "a"} {:gender :M, :name "a"}],
"b" [{:gender :M, :name "b"}]}
If maps prove to be too slow, you can turn them into records:
(defrecord user [name gender])
(def mydata [(map->user {:name "a", :gender :F})
(map->user {:name "b", :gender :M})
(map->user {:name "a", :gender :M})])
... which work as the maps do.
Upvotes: 6