I'm trying to translate the following macro from land of lisp into clojure:
(defmacro tag (name atts &body body)
`(progn (print-tag ',name
(list ,@(mapcar (lambda (x)
`(cons ',(car x) ,(cdr x)))
(pairs atts)))
nil)
,@body
(print-tag ',name nil t)))
But I keep getting stuck with atts requiring 1 more level of evaluation. E.g. the following needs to evaluate t#:
(defmacro tag [tname atts & body]
`(do (print-tag '~tname '[~@(map (fn [[h# t#]] [h# t#]) (pair atts))] nil)
~@body
(print-tag '~tname nil true)))
As it produces stuff like:
(tag mytag [color 'blue size 'big])
<mytag color="(quote blue)" size="(quote big)"><\mytag>
Where I want the attribute to evaluated. If I use "(eval t#)" in the above I fall foul of problems like this:
(defn mytag [col] (tag mytag [colour col]))
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:1)
Any suggestions?
Why does it seem like one less level of evaluation happens in Clojure?
Definitions of supporting functions:
;note doesn't handle nils because I'm dumb
(defn pair [init-lst]
(loop [lst init-lst item nil pairs []]
(if (empty? lst)
pairs
(if item
(recur (rest lst) nil (conj pairs [item (first lst)]))
(recur (rest lst) (first lst) pairs)))))
(defn print-tag [name alst closing]
(print "<")
(when closing
(print "\\"))
(print name)
(doall
(map (fn [[h t]]
(printf " %s=\"%s\"" h t))
alst))
(print ">"))
(For some reason I didn't do the pair function in the same way as the book which means it doesn't handle nils correctly)
Your Clojure definition of tag
quotes everything in the attribute map, while the common lisp version quotes only the names. That's the immediate source of your problems - if you just dropped the '
in front of your vector/map, and then fiddled with the map
to quote the first element, you'd probably be fine.
However, while porting may be a good exercise, this code is not written in The Clojure Way: the printing is a nasty ucky side effect that makes it hard to use print-tag to do anything meaningful; returning a string instead would be much nicer.
(defmacro tag [name attrs & body]
`(str "<"
(clojure.string/join " "
['~name
~@(for [[name val] (partition 2 attrs)]
`(str '~name "=\"" ~val "\""))])
">"
~@body
"</" '~name ">"))
user> (tag head [foo (+ 1 2)] "TEST" (tag sample []))
"<head foo=\"3\">TEST<sample></sample></head>"
Of course, since order doesn't matter, using a map instead of a vector is nicer for the attributes. That would also mean you could drop the (partition 2...)
, since a sequential view of a map comes out as pairs already.
And once we've gotten this far, it turns out that there are already plenty of ways to represent XML as Clojure data structures, so I would never use my above code in a real application. If you want to do XML for real, check out any of Hiccup, prxml, or data.xml.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With