How to extend a sequence (var-args) to separate elements

I want to send var-args functions to a macro, still like var-args. Here is my code:

(defmacro test-macro [& args] `(println (str "count=" ~(count args) "; args=" ~@args ))) (defn test-fn-calling-macro [& args] (test-macro args)) 

The result (test-macro "a" "b" "c") is what I want: count=3; args=abc count=3; args=abc

Output (test-fn-calling-macro "a" "b" "c") : count=1; args=("a" "b" "c") count=1; args=("a" "b" "c") because args is sent as a single argument to the macro. How can I extend this argument in my function to call a macro with three arguments?

I guess I just missed the simple main function, but I can't find it. Thanks


EDIT 2 . My "real" code, shown in the EDIT section below, is not a valid situation for using this technique.

As @Brian pointed out, the xml-to-cass can be replaced with this function:

 (defn xml-to-cass [zipper table key attr & path] (doseq [v (apply zf/xml-> zipper path)] (cass/set-attr! table key attr v))) 

EDIT - the next section is beyond the scope of my original question, but any understanding is welcome

The above code is simply the simplest one I could come with to determine my problem. My real code deals with clj-cassandra and zip filters. It may also look super-engineering, but it's just a toy project, and I'm trying to learn a language at the same time.

I want to parse the XML data found on mlb.com and paste the values ​​found in the cassandra database. Here is my code and thinking behind it.

Step 1 - Function that works fine, but contains code duplication

 (ns stats.importer (:require [clojure.xml :as xml] [clojure.zip :as zip] [clojure.contrib.zip-filter.xml :as zf] [cassandra.client :as cass])) (def root-url "http://gd2.mlb.com/components/game/mlb/year_2010/month_05/day_01/") (def games-table (cass/mk-cf-spec "localhost" 9160 "mlb-stats" "games")) (defn import-game-xml-1 "Import the content of xml into cassandra" [game-dir] (let [url (str root-url game-dir "game.xml") zipper (zip/xml-zip (xml/parse url)) game-id (.substring game-dir 4 (- (.length game-dir) 1))] (doseq [v (zf/xml-> zipper (zf/attr :type))] (cass/set-attr! games-table game-id :type v)) (doseq [v (zf/xml-> zipper (zf/attr :local_game_time))] (cass/set-attr! games-table game-id :local_game_time v)) (doseq [v (zf/xml-> zipper :team [(zf/attr= :type "home")] (zf/attr :name_full))] (cass/set-attr! games-table game-id :home_team v)))) 

The import-game-xml-1 parameter can be, for example, "gid_2010_05_01_colmlb_sfnmlb_1/" . I remove the "gid_" and trailing slash to make it the key in ColumnFamily games in my database.

I found that 3 doseq had a lot of duplication (and there should be more than 3 in the final version). So the code templates using the macro seemed appropriate here (correct me if I am wrong).

Step 2 - Presenting the macro for code templates (still working)

 (defmacro xml-to-cass [zipper table key attr & path] `(doseq [v# (zf/xml-> ~zipper ~@path )] (cass/set-attr! ~table ~key ~attr v#))) (defn import-game-xml-2 "Import the content of xml into cassandra" [game-dir] (let [url (str root-url game-dir "game.xml") zipper (zip/xml-zip (xml/parse url)) game-id (.substring game-dir 4 (- (.length game-dir) 1))] (xml-to-cass zipper games-table game-id :type (zf/attr :type)) (xml-to-cass zipper games-table game-id :local_game_time (zf/attr :local_game_time)) (xml-to-cass zipper games-table game-id :home_team :team [(zf/attr= :type "home")] (zf/attr :name_full)))) 

I find that improvement, but I still see some duplication when reusing the same 3 parameters in my xml-to-cass . I introduced an intermediate function to take care of them.

Step 3 - Adding a function to call a macro (problem here)

 (defn import-game-xml-3 "Import the content of xml into cassandra" [game-dir] (let [url (str root-url game-dir "game.xml") zipper (zip/xml-zip (xml/parse url)) game-id (.substring game-dir 4 (- (.length game-dir) 1)) save-game-attr (fn[key path] (xml-to-cass zipper games-table game-id key path))] (save-game-attr :type (zf/attr :type)) ; works well because path has only one element (save-game-attr :local_game_time (zf/attr :local_game_time)) (save-game-attr :home :team [(zf/attr= :type "home"] (zf/attr :name_full))))) ; FIXME this final line doesn't work 
+6
clojure
source share
4 answers

Here is some simple code that can be highlighted.

Macros are based on code generation. If you want this to happen at runtime, for some reason, you need to compile and evaluate the code at runtime. It can be a powerful technique.

 (defmacro test-macro [& args] `(println (str "count=" ~(count args) "; args=" ~@args ))) (defn test-fn-calling-macro [& args] (test-macro args)) (defn test-fn-expanding-macro-at-runtime [& args] (eval (cons `test-macro args))) (defmacro test-macro-expanding-macro-at-compile-time [& args] (cons `test-macro args)) ;; using the splicing notation (defmacro test-macro-expanding-macro-at-compile-time-2 [& args] `(test-macro ~@args )) (defn test-fn-expanding-macro-at-runtime-2 [& args] (eval `(test-macro ~@args ))) (test-macro "a" "b" "c") ;; count=3; args=abc nil (test-fn-calling-macro "a" "b" "c") ;; count=1; args=("a" "b" "c") nil (test-fn-expanding-macro-at-runtime "a" "b" "c") ; count=3; args=abc nil (test-macro-expanding-macro-at-compile-time "a" "b" "c") ; count=3; args=abc nil (test-macro-expanding-macro-at-compile-time-2 "a" "b" "c") ; count=3; args=abc nil (test-fn-expanding-macro-at-runtime "a" "b" "c") ; count=3; args=abc nil 

If contemplation of the foregoing does not prove enlightenment, can I suggest a couple of my own blog articles?

In this, I look at macros from scratch and how clojure works in particular:

http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html

And in this, I show why generating code at runtime can be useful:

http://www.learningclojure.com/2010/09/clojure-faster-than-machine-code.html

+4
source share

A typical way to use a collection as separate function arguments is to use (apply function my-list-o-args)

 (defn test-not-a-macro [& args] (print args)) (defn calls-the-not-a-macro [& args] (apply test-not-a-macro args)) 

although you won’t be able to use the application because test-macro is a macro. to solve this problem you will need to wrap the test macro in a function call so that you can apply it.

 (defmacro test-macro [& args] `(println ~@args )) (defn calls-test-macro [& args] (eval (concat '(test-macro) (args)))) ;you almost never need eval. (defn calls-calls-test-macro [& args] (calls-test-macro args)) 

This is actually a really good example of one way that macros are hard to compose. (some would say that they cannot be composed cleanly, although I think this is an exaggeration)

+2
source share

Macros are not magical. This is a mechanism for converting code at compile time to equivalent code; they are not used at runtime. The pain you feel is that you are trying to do something that you should not try to do.

I do not know this library, but if cass/set-attr! is a function, I don’t see the reason why the macro you defined should be a macro; this may be a function. You can do what you want if you can rewrite your macro as a function.

+2
source share

Your requirements are not clear. I don’t understand why the macro is needed here for test-macro , if you are not trying to print the invaluable forms provided to your macro.

These features provide the expected results, but this is because your sample data has been self-rated.

 (defn test-args [& args] (println (format "count=%d; args=%s" (count args) (apply str args)))) 

or

 (defn test-args [& args] (print (format "count=%d; args=" (count args))) (doseq [a args] (pr a)) (newline)) 

You can submit other options to get the same result.

Try calling this function with something that does not evaluate itself, and pay attention to the result:

 (test-args (+ 1 2) (+ 3 4)) 

Have you searched for arguments printed as "37" or "(+ 1 2) (+ 3 4)"?

If you were trying to learn about macros and their extension in general, as opposed to solving this particular problem, please set up your question to explore further.

+1
source share

All Articles