Penso che questo dipenda da cosa intendi per "comporre". Direi che è quasi sempre vero ma può essere preso troppo lontano.
Per prima cosa, nota che nella citazione che hai citato aphyr non ha detto "i macro non compongono"; la frase era "non può essere composta funzionalmente". Questa rivendicazione più limitata è indiscutibilmente vera. Le macro in Clojure non sono valori [1]. Non possono essere passati come argomenti ad altre funzioni o macro, non possono essere restituiti come risultato di calcoli e non possono essere memorizzati in strutture di dati.
Ci sono, naturalmente, modi per ovviare a queste limitazioni. Oltre al tuo esempio di #(and %1 %2)
, c'è un senso in cui il codice seguente rappresenta la composizione.
(ns some.macros
(:refer-clojure :exclude [when when-not]))
(defmacro when [test & body]
'(cond ~test (do ~@body)))
(defmacro when-not [test & body]
'(when (not ~test) ~@body))
Ho intenzionalmente utilizzato cond
anziché if
nella definizione di when
perché cond
è a sua volta una macro.
Ma, tornando al punto più grande, #(and %1 %2)
non è in realtà un argomento che le macro compongono, è un argomento che puoi usare macro all'interno delle funzioni. E, poiché abbiamo molti modi potenti di lavorare con le funzioni, questo è generalmente il modo migliore per andare. Usa le macro (quando le funzioni non sono appropriate) per astrarre il codice ripetitivo, ma, quando possibile, esponi la maggior parte delle API possibili come funzioni in modo che possano essere memorizzate in mappe hash, mappate su sequenze di widget , negato con complement
, giustapposto con juxt
e così via.
[1] Sarebbe difficile che sia diversamente. Immaginate se le macro fossero valori e potessero essere passati come argomenti alle funzioni. Come è possibile compilare il codice che ha utilizzato tale macro, dato che la sua espansione non sarebbe stata nota fino al runtime (e potrebbe variare tra le invocazioni della funzione)?
Addendum
Come punto di interesse, considera che le funzioni sono delle macro, sono trattate appositamente dal compilatore.
Controlla questo:
some.macros> (defn when [&form &env test & body]
'(cond ~test (do ~@body)))
#<Var@1a37b98:
#<macros$eval7806$when__7807 some.macros$eval7806$when__7807@d1cf9c>>
some.macros> (when nil nil true 1 2)
(clojure.core/cond true (do 1 2))
some.macros> (. #'when (setMacro))
nil
some.macros> (when true 1 2)
2
Questo è, in effetti, esattamente come funziona defmacro
. (Vedi qui per una spiegazione degli argomenti pseudo-argomenti &form
e &env
).