Reputation: 191
Assum I hava some(more than 20) variables, I want to save them to a file. I don't want to repeat 20 times the same code. I wrote a macro but it gave me an error.
my test case:
;-----------------------------------------------
(defn processor [ some-parameters ]
(let [
;after some operation ,got these data:
date-str ["JN01","JN02","JN03","JN04"];length 8760
date-temperature (map #(str %2 "," %1) [3.3,4.4,5.5,6.6] date-str) ; all vector's length are 8760
date-ws (map #(str %2 "," %1) [0.2,0.1,0.3,0.4] date-str) ;
;... many variables such like date-relative-humidity,date-pressure, name starts with "date-",
; all same size
]
;(doseq [e date-temperature]
; (println e))
(spit "output-variable_a.TXT"
(with-out-str
(doseq [e date-temperature]
(println e))))
;same 'spit' part will repeat many times
))
(processor 123)
; I NEED to output other variables(ws, wd, relative-humidity, ...)
; Output example:
;JN01,3.3
;JN02,4.4
;JN03,5.5
;JN04,6.6
;-----------------------------------------------
what I want is a macro/function I can use this way:
(write-to-text temperature,ws,wd,pressure,theta-in-k,mixradio)
and this macro/function will do the work. I don't know how to write such a macro/function.
My macro post here but it doesn't work:
(defmacro write-array [& rest-variables ]
`(doseq [ vname# '~rest-variables ]
;(println vname# vvalue#)
(println "the vname# is" (symbol vname#))
(println "resolve:" (resolve (symbol (str vname# "-lines"))))
(println "resolve2:" (resolve (symbol (str "ws-lines"))))
(let [ vvalue# 5] ;(var-get (resolve (symbol vname#)))]
;----------NOTE: commented out cause '(symbol vname#)' won't work.
;1(spit (str "OUT-" vname# ".TXT" )
;1 (with-out-str
;1 (doseq [ l (var-get (resolve (symbol (str vname# "-lines"))))]
;1 (println l))))
(println vname# vvalue#))))
I found that the problem is (symbol vname#)
part, this method only works for a GLOBAL variable, cannot bound to date-temperature in the LET form,(symbol vname#)
returns nil.
Upvotes: 2
Views: 970
Reputation: 16194
It looks like you want to write a file of delimited values using binding names and their values from inside a let
. Macros transform code during compilation and so they cannot know the run-time values that the symbols you pass are bound to. You can use a macro to emit code that will be evaluated at run-time:
(defmacro to-rows [& args]
(let [names (mapv name args)]
`(cons ~names (map vector ~@args))))
(defn get-stuff []
(let [nums [1 2 3]
chars [\a \b \c]
bools [true false nil]]
(to-rows nums chars bools)))
(get-stuff)
=> (["nums" "chars" "bools"]
[1 \a true]
[2 \b false]
[3 \c nil])
Alternatively you could produce a hash map per row:
(defmacro to-rows [& args]
(let [names (mapv name args)]
`(map (fn [& vs#] (zipmap ~names vs#)) ~@args)))
=> ({"nums" 1, "chars" \a, "bools" true}
{"nums" 2, "chars" \b, "bools" false}
{"nums" 3, "chars" \c, "bools" nil})
You would then need to write that out to a file, either using data.csv or similar code.
To see what to-rows
expands to, you can use macroexpand
. This is the code being generated at compile-time that will be evaluated at run-time. It does the work of getting the symbol names at compile-time, but emits code that will work on their bound values at run-time.
(macroexpand '(to-rows x y z))
=> (clojure.core/cons ["x" "y" "z"] (clojure.core/map clojure.core/vector x y z))
As an aside, I'm assuming you aren't typing thousands of literal values into let
bindings. I think this answers the question as asked but there could likely be a more direct approach than this.
Upvotes: 2
Reputation: 136
You can first capture the variable names and their values into a map:
(defmacro name-map
[& xs]
(let [args-list# (cons 'list (map (juxt (comp keyword str) identity) xs))]
`(into {} ~args-list#)))
If you pass the var names to the macro,
(let [aa 11
bb 22
cc 33]
(name-map aa bb cc))
It gives you a map which you can then use for any further processing:
=> {:aa 11, :bb 22, :cc 33}
(def result *1)
(run!
(fn [[k v]] (println (str "spit file_" (name k) " value: " v)))
result)
=>
spit file_aa value: 11
spit file_bb value: 22
spit file_cc value: 33
Edit: Just noticed it's similar to Taylor's macro. The difference is this one works with primitive types as well, while Taylor's works for the original data (vars resolving to collections).
Upvotes: 0
Reputation: 51501
I think you are looking for the function name
. To demonstrate:
user=> (defmacro write-columns [& columns]
(let [names (map name columns)]
`(str ~@names)))
#'user/write-columns
user=> (write-columns a b c)
"abc"
Upvotes: 0