macro | since v0.0-927 | clojure.core/defmacro | Edit |
(defmacro name doc-string? attr-map? [params*] body)
(defmacro name doc-string? attr-map? ([params*] body) + attr-map?)
Defines a macro, which is essentially a function that runs at compile time. Macros can be used to define syntactic constructs which would require primitives or built-in support in other languages.
Using macros is as easy as using functions, but writing them is a little more difficult. Also, creating macros is generally discouraged if you can accomplish the same goal with a function.
There is a strict rule for when you can use defmacro
-- you can only use it
in what we call a macro namespace, effectively forcing you to separate your
compile time and runtime code.
A side effect of this is that you cannot use defmacro
from a REPL. Sorry!
This strict rule is due to the nature of differing compile time environments for the optimized "ClojureScript JVM" compiler and the newer bootstrapped "ClojureScript JS" compiler.
In order to create macros that are portable between either compiler version,
you must place macros in a .cljc
file, but a .clj
file is sufficient if no
reader conditionals are needed. Why would they be needed?
Because ClojureScript macro namespaces may be handed off to Clojure for
evaluation, depending on the compiler version:
compiler version | macro namespaces evaluated by |
---|---|
ClojureScript JVM | Clojure |
ClojureScript JS | ClojureScript |
Please see the examples section below for a more concrete look.
Here is a str->int
macro that works for either ClojureScript compiler
version. It simply expands to a js/parseInt
call:
;; in macros.clj
(ns foo.macros)
;; expands to a runtime call
(defmacro str->int [s]
`(js/parseInt s))
If we want to evaluate the conversion at compile time instead of expanding it
to a runtime call, we must use reader conditionals (in a .cljc
file) to
choose the function appropriate for each compiler's evaluation environment.
;; in macros.cljc
(ns foo.macros)
;; expands to the result of the conversion
(defmacro str->int [s]
#?(:clj (Integer/parseInt s)
:cljs (js/parseInt s)))
Like defn, but the resulting function name is declared as a macro and will be used as a macro by the compiler when it is called.
(core/defn defmacro
{:arglists '([name doc-string? attr-map? [params*] body]
[name doc-string? attr-map? ([params*] body)+ attr-map?])
:macro true}
[&form &env name & args]
(core/let [prefix (core/loop [p (core/list (vary-meta name assoc :macro true)) args args]
(core/let [f (first args)]
(if (core/string? f)
(recur (cons f p) (next args))
(if (map? f)
(recur (cons f p) (next args))
p))))
fdecl (core/loop [fd args]
(if (core/string? (first fd))
(recur (next fd))
(if (map? (first fd))
(recur (next fd))
fd)))
fdecl (if (vector? (first fdecl))
(core/list fdecl)
fdecl)
add-implicit-args (core/fn [fd]
(core/let [args (first fd)]
(cons (vec (cons '&form (cons '&env args))) (next fd))))
add-args (core/fn [acc ds]
(if (core/nil? ds)
acc
(core/let [d (first ds)]
(if (map? d)
(conj acc d)
(recur (conj acc (add-implicit-args d)) (next ds))))))
fdecl (seq (add-args [] fdecl))
decl (core/loop [p prefix d fdecl]
(if p
(recur (next p) (cons (first p) d))
d))]
`(let [ret# ~(cons `defn decl)]
(set! (. ~name ~'-cljs$lang$macro) true)
ret#)))