如何在Clojure中使这个宏可变参数?

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的建议,以下是一些改进的宏,它们不使用"严重错误"的eval并包含一些小型测试:

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))

打印["(* 6 7) ~~> 42"]并返回42

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}"]

并返回{: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问题,但我还是屈服了,只使用上面的那个就不用担心括号了。