Learning Clojure/Macros
Macros are functions which effectively allow us to create our own syntactical conveniences. For many Lispers, macros are the essential feature which make Lisp Lisp.
Simple Macro
[edit | edit source]If you understand the reader and evaluator, there actually isn't all that much more to understand about the operation and creation of macros, for a macro is simply a regular function that is called in a special way. When called as a macro, a function takes its arguments unevaluated and returns an expression to be evaluated in place of the call to the macro. A very simple (and pointless) macro would be one that simply returns its argument:
(def pointless (fn [n] n))
Whatever is passed to this macro---a list, a symbol, whatever---will be returned unmolested and then evaluated after the call. Effectively, calling this macro is pointless:
(pointless (+ 3 5)) ; pointless returns the list (+ 3 5), which is then evaluated in its place
(+ 3 5) ; may as well just write this instead
But as we defined pointless above, it is just a regular function, not a macro. To make it a macro, we need to attach the key-value pair :macro true
as metadata to the Var mapped to pointless by the def
. There are a number of ways to do this, but most commonly we would simply define the function as a macro with the provided macro clojure/defmacro:
(defmacro pointless [n] n) ; define a macro pointless that takes one parameter and simply returns it
A macro that does something
[edit | edit source]An actually useful macro typically returns a syntax-quoted list expression. Take, for instance, the case of wanting to do certain things when a DEBUG flag is on and not do them at all when the flag is off. Start by defining the flag:
(def DEBUG true)
Now, we want to do certain things when it's true and not when they aren't. If we define a function:
(defn on-debug-fn [& args]
(when DEBUG
(eval `(do ~@args)))) ; Done this way to expand the list of args.
Then (on-debug-fn "Debug") does indeed only return "Debug" when DEBUG is true. However, (on-debug-fn (println "Debug")) always prints "Debug". This is because, for a function, the arguments are always evaluated and it comes with a side effect: printing "Debug".
What we need, instead, is a macro. Then it becomes possible to check the DEBUG flag without evaluating the forms passed to it.
(defmacro on-debug [& body]
`(when DEBUG
(do ~@body)))
So, let's look at what that does:
(macroexpand-1 '(on-debug (println "Debug"))) => (clojure.core/when user/DEBUG (do (println "Debug")))
(macroexpand-1 ...) is a function that shows what a given macro expands into. So it checks the value of user/DEBUG and only evaluates the body if debug is not false. Looking closer:
(macroexpand
'(on-debug
(println "Debug"))) => (if* user/DEBUG
(do
(do
(println "Debug"))))
(macroexpand ... ) is a function that expands every macro within the form passed to it. What this shows is that (when ... ) is actually a macro that expands into a check using (if* ... ) and a (do ... ) block, thus neatly rendering our (do ... ) block unnecessary.
So the final version of the macro is:
(defmacro on-debug [& body]
`(when DEBUG
~@body))
Now, what if we want multiple debug levels, where 1 is essential information and 3 is painful spam?
(defmacro on-debug-level [level & body]
`(when (and DEBUG
(<= ~level DEBUG))
~@body))
level is prefixed with an unquote symbol so that it isn't taken as belonging to the namespace. DEBUG is not prefixed with an unquote symbol because we want to be able to change debug levels without having to re-evaluate every function that contains an (on-debug-level ...) form.
If, instead, we had used ~DEBUG in the macro, when a macro using (on-debug-level ... ) was evaluated, ~DEBUG would be replaced with the value of user/DEBUG. This means that, if the debug level were later changed, all of those macros would also have to be re-evaluated in order to put the new value into the conditions.
Testing that in the REPL, (on-debug-level 2 (println "Debug")) indeed prints "Debug" when the debug level is 2 and does nothing when the debug level is 1. That can, of course, be used in a function so that there's a single way to print a debugging string.
(defn debug-println [level st]
(on-debug-level level
(println st)))
Macros as control structures
[edit | edit source]This macro assumes a function (get-connection) that opens and returns a connection to a database. It provides a typical Lisp with- idiom, where it binds *conn* to the connection, runs the body of the macro, closes the connection and returns the body's return value.
(defonce *conn* nil)
This binds a var, *conn*, which will be used in the body of the macro to refer to the database connection.
(defmacro with-connection [& body]
`(binding [*conn* (get-connection)]
(let [ret# (do ~@body)]
(.close *conn*)
ret#)))
Stepping through this:
(defmacro with-connection [& body] ...)
This uses the variable-argument syntax in order to get the arguments as a list.
`(binding ...
The backtick operator indicates that everything within this form should be quoted and not evaluated (unless preceded by the unquote operator (~)). Thus, binding becomes clojure.core/binding and everything with it is built as a list rather than as a series of function calls.
`(binding [*conn* (get-connection)]
This takes the var *conn* and binds it so that within this form, it has the value returned by (get-connection). In other words, operations within a (with-connection ... ) form can operate on *conn* as a database connection.
Note here that the function (get-connection) is not evaluated until the macro is actually used. This is due to the backtick before (binding ... If, on the other hand, we wanted to evaluate the function when the macro is created, the unquote (~) operator would be used:
`(binding [*conn* ~(get-connection)]
This calls (get-connection) once, when the macro is defined, and all future use of that macro would use the value initially returned by ~(get-connection).
(let [ret# (do ~@body)]
This statement is a bit more complicated. ret# creates a gensymmed name; a unique name, ensuring that, if the symbol ret is used within the body of a (with-connection ... ) form, it will not come into conflict with the symbol used in the macro definition.
The latter half of the statement unpacks the body and passes it to do. Variable arguments passed to a macro (as with [& body] above) are given as a list. The ~@ (unquote-splicing) operator replaces a list with the values contained in it.
Without the ~@ operator, the statement would look something like this:
`(do (list 1 2 3)) => (do (clojure.core/list 1 2 3))
With the ~@ operator, the list is replaced by the values within, giving:
`(do ~@(list 1 2 3)) => (do 1 2 3)
Thus the statement:
(let [ret# (do ~@body)]
Binds the name ret# to the value returned by evaluating the body. The reason for this is to return the value of the body form, rather than the value returned by closing the database.
Finally:
(.close *conn*) ret#)))
Calls the connection's close method and returns the body's return value. What this means, all told, is that:
(with-connection (str *conn*))
Opens the database connection, binds it to *conn*, catches the value returned by the body (in this case, just the string representation of the connection), closes the database connection and returns the return value of the body.