The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code.
A better example would have been the (when) macro.
In Clojure you have (cond) and (if) for conditional evaluations. If has the form
(if (cond)
(when-true)
(when-false))
e.g. (if (= 1 1)
(println "1 = 1")
(println "uh-oh the unviverse is broken!"))
=> "1 = 1"
Now what if we want to do more than 1 statement on the true path and we dont care about the false path? (if (= 1 1)
(println "1 = 1")
(println "also hello"))
This won't work as now "also hello" is the false path.. We can use (do) to specify multiple things to be done. (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
1=1
Also hello
But writing (do) is a bit of a pain, so an alternatie would be (when) (when (= 1 1)
(println "1=1")
(println "Also hello"))
Will print, when run, "1=1"
"Also hello"
COuld we implement this as a function, sure? BUt we would have to quote the function calls when we pass them so they aren't evaluated and then eval them if true. You could do it, but it would be messy.Essentially, we want to write
(when (= 1 1)
(println "1=1")
(println "Also hello"))
But want the code that gets compiled to be: (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
So we want to extend the language so we can write (when) and the compiler will write us an (if (do))This is incredibly easy in Clojure. We just need to create a list where the first item in the list is `if`, the second item is the conditional we pass to the macro, the 3rd item is a sublist, of which its first item is `do` followed by the list / functions to execute when our test evaluates to true!
(defmacro when [test & body]
(list 'if test (cons 'do body)))
This is just the source code for the actual clojure core when macro. But you can see how easy this is!If we run the macro expansion on when we can see the code that it generates:
(macroexpand '(when (= 1 1)
(println "1=1")
(println "Also hello")))
=> (if (= 1 1) (do (println "1=1") (println "Also hello")))
Another example would be the reader macro:Say we have the nested function calls:
(reduce + (filter even? (range 1 11)))
=> 30
After you have a lot of nesting this can get difficult to read, so you have a macro ->> which is the threading macro. This takes a series of functions and threads the result of each function as the input to the next. (->> (range 1 11)
(filter even?)
(reduce +))
The source code for this is almost as simple as (when) (defmacro ->>
[x & forms]
(loop [x x, forms forms]
(if forms
(let [form (first forms)
threaded (if (seq? form)
(with-meta `(~(first form) ~@(next form) ~x) (meta form))
(list form x))]
(recur threaded (next forms)))
x)))
The ` ~ and ~@ are just doing some quoting and quote splicing to determine when we want stuff evaluated.Basically, using macros you do things like control symbolic resolution time, extend the compiler to create a DSL spcific to your domain and reduce boiler plate code.
That's before you start getting into properly weird stuff like anaphoric macros.