How do I make this macro variadic in clojure?
我想做一个叫做ds的东西
1 2 | (let [a 2] (ds a)) |
->
1 | "a->2" |
和
1 2 | (let [a 1 b 2 c 3] (ds a b c)) |
->
1 | "a->1, b->2, c->3" |
到目前为止,我已经了解到:
1 2 3 4 5 | (defmacro ds3 [a b c] `(clojure.string/join"," [(str '~a"->" ~a) (str '~b"->" ~b) (str '~c"->" ~c)])) |
这似乎可行:
1 2 | (let [ a 1 b 2 c 3] (ds3 a b c)) ;"1->1, 2->2, 3->3" |
显然我可以定义ds1 ds2 ds3等等...,但是我想知道如何使其可变。
干得好:
1 2 3 4 | (defmacro ds [& symbols] `(clojure.string/join"," ~(into [] (map (fn [s] `(str ~(name s)"->" ~s)) symbols)))) |
Ankur的答案可能是最实用的,但是他将许多工作推迟到了运行时,这些工作可以在宏扩展时完成。这是一个有用的练习,并且可以很好地演示power宏,以了解您在编译时可以完成多少工作:
1 2 3 4 5 6 7 8 9 | (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) |
这样可以避免在运行时生成任何临时对象,并且可以执行绝对最小数量的字符串连接。
编辑:
感谢@amalloy的建议,以下是一些改进的宏,它们不使用"严重错误"的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (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))))) |
1
2
3
4
5
6 (* 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | (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)))) |
1
2
3
4
5 ["(* 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"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | (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)))) |
1
2
3
4
5 {"(* 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"}
已弃用:
我将其留作说明,以说明不应该做什么。
这是一种变体,适用于任何一种表达方式(我认为)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | (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#)) |
一些样本:
1 (pdump (* 6 7))
打印
1 (pdump (* 7 6) (/ 1 0) (into {} [[:a 1]]))
版画
1 2 3 | ["(* 7 6) ~~> 42" "(/ 1 0) ~~> java.lang.ArithmeticException: Divide by zero" "(into {} [[:a 1]]) ~~> {:a 1}"] |
并返回
编辑:
我试图摆脱打印输出中的外括号,即
1 2 3 4 5 6 7 8 | (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#))) |
不起作用,我不确定为什么。它不打印输出,但是宏扩展看起来不错。可能是nREPL或REPL问题,但我还是屈服了,只使用上面的那个就不用担心括号了。