How to make this macro option in clojure?
I want to do something called ds so that
(let [a 2]
(ds a))
->
"a->2"
and
(let [a 1 b 2 c 3]
(ds a b c))
->
"a->1, b->2, c->3"
And so far I have come to:
(defmacro ds3 [a b c]
`(clojure.string/join ", "
[(str '~a "->" ~a)
(str '~b "->" ~b)
(str '~c "->" ~c)]))
What works:
(let [ a 1 b 2 c 3]
(ds3 a b c)) ; "1->1, 2->2, 3->3"
Obviously, I can define ds1 ds2 ds3 etc., but I was thinking how to make it variable?
Ankur's answer is probably the most practical, but it postpones most of the work at runtime, which can be done during macro expansion. This is a useful exercise, and a nice demonstration of powerful macros can lead to how much work you can do at compile time:
(defmacro ds [& args]
`(str ~(str (name (first args)) "->")
~(first args)
~@(for [arg (rest args)
clause [(str ", " (name arg) "->") arg]]
clause)))
(macroexpand-1 '(ds a b c))
=> (clojure.core/str "a->" a ", b->" b ", c->" c)
This avoids the creation of any temporary objects at run time and the absolute minimum number of string concatenations.
@amalloy, , " " eval -:
(import 'java.lang.ArithmeticException)
(defmacro explain-expr
"Produce a string representation of the unevaluated expression x, concatenated to
an arrow and a string representation of the result of evaluating x, including
Exceptions should they arise."
[x]
`(str ~(str x) " ~~> "
(try ~x (catch Exception e# (str e#)))))
(println (explain-expr (* 42 42)))
(println (explain-expr (let [x 1] x)))
(println (explain-expr (/ 6 0)))
(println (let [x 1] (explain-expr x)))
(let [y 37] (println (explain-expr (let [x 19] (* x y)))))
(let [y 37] (println (explain-expr (let [y 19] (* y y)))))
(* 42 42) ~~> 1764
(let [x 1] x) ~~> 1
(/ 6 0) ~~> java.lang.ArithmeticException: Divide by zero
x ~~> 1
(let [x 19] (* x y)) ~~> 703
(let [y 19] (* y y)) ~~> 361
(defmacro explain-exprs
"Produce string representations of the unevaluated expressions xs, concatenated
to arrows and string representations of the results of evaluating each
expression, including Exceptions should they arise."
[& xs]
(into [] (map (fn [x]
`(str ~(str x) " ~~> "
(try ~x (catch Exception e# (str e#)))))
xs)))
(clojure.pprint/pprint
(let [y 37]
(explain-exprs
(* 42 42)
(let [x 19] (* x y))
(let [y 19] (* y y))
(* y y)
(/ 6 0))))
["(* 42 42) ~~> 1764"
"(let [x 19] (* x y)) ~~> 703"
"(let [y 19] (* y y)) ~~> 361"
"(* y y) ~~> 1369"
"(/ 6 0) ~~> java.lang.ArithmeticException: Divide by zero"]
(defmacro explanation-map
"Produce a hashmap from string representations of the unevaluated expressions
exprs to the results of evaluating each expression in exprs, including
Exceptions should they arise."
[& exprs]
(into {}
(map (fn [expr]
`[~(str expr)
(try ~expr (catch Exception e# (str e#)))])
exprs)))
(clojure.pprint/pprint
(let [y 37]
(explanation-map
(* 42 42)
(let [x 19] (* x y))
(let [y 19] (* y y))
(* y y)
(/ 6 0))))
{"(* 42 42)" 1764,
"(let [x 19] (* x y))" 703,
"(let [y 19] (* y y))" 361,
"(* y y)" 1369,
"(/ 6 0)" "java.lang.ArithmeticException: Divide by zero"}
I leave this as an illustration of what not to do.
Here's a variation that will work on any expression (I think)
(defmacro dump-strings-and-values
"Produces parallel vectors of printable dump strings and values. A dump string
shows an expression, unevaluated, then a funny arrow, then the value of the
expression."
[& xs]
`(apply map vector ;; transpose
(for [x# '~xs
v# [(try (eval x#) (catch Exception e# (str e#)))]]
[(str x# " ~~> " v#) v#])))
(defmacro pdump
"Print dump strings for one or more given expressions by side effect; return
the value of the last actual argument."
[& xs]
`(let [[ss# vs#]
(dump-strings-and-values ~@xs)]
(clojure.pprint/pprint ss#)
(last vs#))
Some examples:
(pdump (* 6 7))
prints ["(* 6 7) ~~> 42"]and returns 42.
(pdump (* 7 6) (/ 1 0) (into {} [[:a 1]]))
prints
["(* 7 6) ~~> 42"
"(/ 1 0) ~~> java.lang.ArithmeticException: Divide by zero"
"(into {} [[:a 1]]) ~~> {:a 1}"]
and returns {:a 1}.
EDIT
My attempt to get rid of external brackets in printed form, namely
(defmacro vdump
"Print dump strings for one or more given expressions by side effect; return
the value of the last actual argument."
[& xs]
`(let [[ss# vs#]
(dump-strings-and-values ~@xs)]
(map clojure.pprint/pprint ss#)
(last vs#)))
NOT working, and I'm not sure why. It does not print the output, but the macro distribution looks good. Maybe the problem is nREPL or REPL, but I gave and just used the above and do not worry about parentheses.