2013-02-04 2 views
5

Voglio fare qualcosa chiamato ds in modo cheCome faccio a rendere questa macro variadic in clojure?

(let [a 2] 
    (ds a)) 

->

"a->2" 

e

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

->

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

E finora ho ottenuto come quanto:

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

che sembra funzionare:

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

Ovviamente posso definire DS1 DS2 DS3 ecc ..., ma mi chiedevo come farlo variadic?

risposta

9

Qui si va: risposta

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

Perfetto! Grazie! –

7

di Ankur è probabilmente il più pratico, ma è rinviare un sacco di lavoro a tempo di esecuzione, che potrebbe essere fatto al momento macroexpansion. E 'un esercizio utile, e una bella dimostrazione delle macro di potenza può portare, per vedere come gran parte del lavoro che si può fare al momento della compilazione:

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

questo modo si evita la costruzione di tutti gli oggetti temporanei in fase di esecuzione, e fa l'assoluto numero minimo di concatenazioni di stringhe.

1

EDIT:

grazie ai suggerimenti di @amalloy, qui ci sono alcune macro migliorate che non utilizzano il 'male male' eval e comprendono alcuni mini-test:

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

DISAPPROVATO:

che sto lasciando questo come un esempio di ciò che non da fare.

Ecco una variante che funziona su qualsiasi tipo di espressione (credo)

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

Alcuni esempi:

(pdump (* 6 7)) 

stampe ["(* 6 7) ~~> 42"] e restituisce 42.

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

stampe

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

e restituisce {:a 1}.

EDIT:

Il mio tentativo di sbarazzarsi delle staffe esterne nell'output stampato, vale a dire

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

fa NON lavoro, e io non sono sicuro perché. Non stampa l'output, ma l'espansione della macro sembra buona. Potrebbe essere un problema nREPL o REPL, ma ho ceduto e ho appena utilizzato quello sopra e non mi preoccupo molto delle parentesi.

+0

Non funziona con una chiusura perché stai usando eval. eval non ha accesso all'ambiente lessicale, quindi non può funzionare. Fortunatamente, è possibile fare esattamente quello che stai facendo senza l'uso di eval, come descritto nelle altre risposte. Vi lascio l'esercizio di usare la stessa tecnica delle altre risposte per fornire le caratteristiche più generali che stavate cercando. (Ho downvoted questo perché usare 'eval' per questo è molto sbagliato, ma sarei felice di convertirlo in un upvote se lo facessi al momento della macro). – amalloy

+0

Oh, leggendo la tua risposta più attentamente vedo che ho frainteso quello che stavi dicendo non funziona. Quello che volevo dire inizialmente era che '(let [x 1] (vdump x))' non funzionerà a causa dell'ambito dell'ambito, ma probabilmente non l'hai provato. Piuttosto, il tuo codice non riesce a stampare perché [map is pigro] (https://stackoverflow.com/q/10857690/625403). – amalloy

+0

@amalloy oh, sì, ho dimenticato la mappa pigra. –