2013-02-04 8 views
5

Ich möchte etwas namens ds machen, so dassWie mache ich dieses Makro variadic in clojure?

(let [a 2] 
    (ds a)) 

->

"a->2" 

und

(let [a 1 b 2 c 3] 
    (ds a b c)) 

->

"a->1, b->2, c->3" 

Und so weit ich habe habe als Was:

(defmacro ds3 [a b c] 
    `(clojure.string/join ", " 
      [(str '~a "->" ~a) 
      (str '~b "->" ~b) 
      (str '~c "->" ~c)])) 

Welche scheint zu funktionieren:

(let [ a 1 b 2 c 3] 
    (ds3 a b c)) ; "1->1, 2->2, 3->3" 

Natürlich kann ich ds1 ds2 ds3 etc definieren ..., aber ich fragte mich, wie es variadische zu machen?

Antwort

9

Hier gehen Sie:

(defmacro ds [& symbols]                                
    `(clojure.string/join ", "                               
         ~(into [] 
          (map (fn [s] `(str ~(name s) "->" ~s)) symbols))))                 
+0

Perfekt! Vielen Dank! –

7

Ankur Antwort ist wohl die praktischste, aber er ist viel von der Arbeit Laufzeit aufzuschieben, die bei macroexpansion Zeit getan werden könnte. Es ist eine nützliche Übung, und eine schöne Demonstration der Macht Makros bringen, um zu sehen, wie viel von der Arbeit, die Sie bei der Kompilierung tun können:

(defmacro ds [& args] 
    `(str ~(str (name (first args)) "->") 
     ~(first args) 
     [email protected](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) 

Dies vermeidet temporäre Objekte zur Laufzeit bauen, und tut die absoluten Mindestanzahl von String-Verkettungen

1

EDIT:

Dank Vorschläge von @amalloy, hier sind einige verbesserte Makros, die die nicht 'schlecht falsch' verwenden eval und umfassen einige Mini-Tests:

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

DEPRECATED:

Ich verlasse diese in als Illustration dessen, was nicht zu tun.

Hier ist eine Variante, die auf jede Art von Ausdruck (glaube ich)

(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 [email protected])] 
    (clojure.pprint/pprint ss#) 
    (last vs#)) 

Einige Proben funktionieren:

(pdump (* 6 7)) 

druckt ["(* 6 7) ~~> 42"] und gibt 42.

(pdump (* 7 6) (/ 1 0) (into {} [[:a 1]])) 

druckt

["(* 7 6) ~~> 42" 
"(/ 1 0) ~~> java.lang.ArithmeticException: Divide by zero" 
"(into {} [[:a 1]]) ~~> {:a 1}"] 

und gibt {:a 1}.

EDIT:

Mein Versuch der äußeren Klammern in der gedruckten Ausgabe, um loszuwerden, nämlich

(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 [email protected])] 
    (map clojure.pprint/pprint ss#) 
    (last vs#))) 

tut NICHT Arbeit, und ich bin mir nicht sicher, warum. Die Ausgabe wird nicht gedruckt, aber die Makroerweiterung sieht gut aus. Könnte ein nREPL- oder REPL-Problem sein, aber ich gab nach und nutze einfach das obige und mache mir keine Sorgen um die Klammern.

+0

Es funktioniert nicht mit einer Schließung, weil Sie eval verwenden. eval hat keinen Zugriff auf die lexikalische Umgebung, daher kann dies nicht funktionieren. Glücklicherweise ist es möglich, genau das zu tun, was Sie tun, ohne eval zu verwenden, wie in den anderen Antworten beschrieben. Ich überlasse es Ihnen, die gleiche Technik wie die anderen Antworten zu verwenden, um die allgemeineren Merkmale zu erreichen, die Sie anstrebten. (Ich habe das heruntergeregelt, weil 'eval' dafür sehr falsch ist; ich würde gerne zu einem upvote konvertieren, wenn du es stattdessen im Makro-Modus machst). – amalloy

+0

Oh, ich lese deine Antwort sorgfältiger. Ich sehe, dass ich falsch verstanden habe, was du gesagt hast, funktioniert nicht. Was ich zuerst meinte war, dass '(lassen Sie [x 1] (vdump x))' wird nicht funktionieren wegen des Scoping, aber Sie haben es wahrscheinlich nicht versucht. Stattdessen wird Ihr Code nicht gedruckt, da [map faul ist] (https://stackoverflow.com/q/10857690/625403). – amalloy

+0

@amalloy oh, ja, ich habe die faule Karte vergessen. –

Verwandte Themen