2012-02-14 18 views
23

In clojure, apply non può essere applicato a una macro. Ad esempio, (apply and [true false]) solleva un'eccezione. Stavo pensando seguente soluzione:In clojure, come applicare una macro a un elenco?

(defmacro apply-macro[func args] `(~func [email protected])) 

A prima vista, sembrava funzionare abbastanza bene:

(apply-macro and [true 5]); 5 
(apply-macro and [true 5 0]); 0 
(let [a 0] (apply-macro and [true a])); 0 

Ma, quando ho passato a una variabile che punta a un vettore, è crollato.

(let [a [true]] (apply-macro and a)); java.lang.IllegalArgumentException: 
    ;Don't know how to create ISeq from: clojure.lang.Symbol 

Che delusione !!!!

Qualche idea su come risolvere apply-macro?

risposta

24

Il problema è che a è solo un simbolo in fase di compilazione. Quindi non c'è modo per una macro in fase di compilazione per vedere cosa contiene e fare l'espansione necessaria. Di conseguenza, è necessario espandere la macro in fase di esecuzione utilizzando eval.

Un modo per farlo è solo per avvolgere la macro in una funzione che chiama eval, che può essere fatto con questo pratico "functionize" macro:

(defmacro functionize [macro] 
    `(fn [& args#] (eval (cons '~macro args#)))) 

(let [a [true]] (apply (functionize and) a)) 
=> true 

Se ti piace, si potrebbe anche definire applicarsi -Macro in termini di functionize:

(defmacro apply-macro [macro args] 
    `(apply (functionize ~macro) ~args)) 

(let [a [true false]] (apply-macro and a)) 
=> false 

detto tutto questo, penso ancora che la cosa migliore da fare è di evitare le macro del tutto quando non sono realmente necessari: aggiungono ulteriore complessità e sono più riservati ai casi in cui hai davvero bisogno di compilare la generazione del codice temporale. In questo caso non è così: Alex Taggart's answer fornisce un buon esempio di come raggiungere un obiettivo simile senza macro, che è probabilmente più appropriato nella maggior parte delle situazioni.

+0

@YehonathanSharvit: il punto è che '(a-macro e a) 'viene espanso a' (e ) 'alla ** lettura ** volta, ma' a' viene definito solo al ** tempo ** di valutazione. Quindi il 'unquote-splicing' di' a' (es. '~ @ A') fallisce, perché' a' non è ancora stato definito. – liwp

+0

Certo, 'a' è un vettore in fase di esecuzione. Ma è inutile se si vuole costruire un modulo '(e ....)' con il contenuto di 'a' in fase di compilazione, poiché il compilatore non può vedere il contenuto di' a'. Quindi è necessario richiamare il compilatore * di nuovo * in fase di esecuzione una volta che si conosce cosa contiene 'a', quindi la necessità di eval. È un po 'complicato, ma spero che la logica abbia senso ..... – mikera

+0

'Functionize' sembra fantastico !!! Sei l'autore? Ci sono dei limiti? – viebel

27

Non è così.

Le macro vengono espanse durante la valutazione/tempo di compilazione, non in fase di runtime, quindi l'unica informazione che possono utilizzare sono gli argomenti passati, ma non ciò che gli argomenti valutano in runtime. Ecco perché funziona un vettore letterale, perché quel vettore letterale è lì in fase di compilazione, ma a è solo un simbolo; valuterà solo un vettore in fase di esecuzione.

Per avere il comportamento and -like per gli elenchi, utilizzare (every? identity coll).

Per avere il comportamento or -like per gli elenchi, utilizzare (some identity coll).

1

Ovviamente, la risposta corretta è non fare questo. Ma, dato che non posso resistere a un buon trucco:

(defmacro apply-macro 
    "Applies macro to the argument list formed by prepending intervening 
    arguments to args." 
    {:arglists '([macro args] 
       [macro x args] 
       [macro x y args] 
       [macro x y z args] 
       [macro a b c d & args])} 
    [macro & args+rest] 
    (let [args (butlast args+rest) 
     rest-args (eval (last args+rest))] 
    `(eval 
     (apply (deref (var ~macro)) 
       '(~macro [email protected] [email protected]) 
       nil 
       [email protected](map #(list 'quote %) args) 
       '~rest-args)))) 

utilizzo:

hackery> (->> (range 5) rest rest rest rest) 
(4) 
hackery> (apply-macro ->> (range 5) (repeat 4 'rest)) 
(4) 

Qualifiche:

  1. La macro non dovrebbe essere citato, e gli argomenti successivi sono passati non valutata alla macro.Tuttavia, l'argomento "resto" è valutato e deve essere valutato in un elenco di simboli o moduli, ognuno dei quali verrà passato non valutato alla macro.
  2. Questo non funzionerà con i macro che fanno uso dell'argomento &env.
0

Questo approccio non funziona se la lista degli argomenti può avere durata illimitata, ma se avete solo bisogno di applicare alle liste di lunghezza fino a n si può fare una funzione wrapper con n arietà:

user> (defmacro foo [& rest] `(println [email protected])) 
#'user/foo 
user> (apply foo [1 2]) 
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'user/foo, compiling:(*cider-repl repo*:865:7) 
user> (defn foo-up-to-ten-args 
    ([a]     (foo a)) 
    ([a b]     (foo a b)) 
    ([a b c]    (foo a b c)) 
    ([a b c d]    (foo a b c d)) 
    ([a b c d e]   (foo a b c d e)) 
    ([a b c d e f]   (foo a b c d e f)) 
    ([a b c d e f g]  (foo a b c d e f g)) 
    ([a b c d e f g h]  (foo a b c d e f g h)) 
    ([a b c d e f g h i] (foo a b c d e f g h i)) 
    ([a b c d e f g h i j] (foo a b c d e f g h i j))) 
#'user/foo-up-to-ten-args 
user> (apply foo-up-to-ten-args [1 2]) 
1 2 
nil 
user> (apply foo-up-to-ten-args (range 0 10)) 
0 1 2 3 4 5 6 7 8 9 
nil 
user> (apply foo-up-to-ten-args (range 0 11)) 
ArityException Wrong number of args (11) passed to: user/foo-up-to-ten-args clojure.lang.AFn.throwArity (AFn.java:429) 

Nel mio caso questo ha fatto ciò di cui avevo bisogno senza eval.

0

Quando si applica args ad un macro condizionali quali or, case, cond & condp per esempio. È possibile utilizzare le funzioni some, partition. Il singolo clausola else è lasciato fuori in questi esempi, ma può essere aggiunto piuttosto facilmente

;apply to the 'or' macro 
(some identity [nil false 1 2 3]) 
=> 1 

;apply to the 'case' macro. 
(some 
    (fn [[case value]] 
    (and (= case 2) value)) 
    (partition 2 [1 "one" 2 "two" 3 "three"])) 
=> "two" 

;apply to the 'cond' macro 
(some 
    (fn [[case value]] 
    (and case value)) 
    (partition 2 [false "one" true "two" false "three" :else "four"])) 
=> "two" 

;apply to the 'condp' macro 
(let [[f v & args] [= 2 1 "one" 2 "two" 3 "three"]] 
    (some 
    (fn [[case value]] 
     (and (f case v) value)) 
    (partition 2 args))) 

every? può essere utilizzato per la and macro

;apply to the 'and' macro 
(every? identity [true true true]) 
=> true 
Problemi correlati