user2545464
user2545464

Reputation: 191

How to bind var's name and value in the clojure macro?

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

Answers (3)

Taylor Wood
Taylor Wood

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

kbsant
kbsant

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

Svante
Svante

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

Related Questions