Getting to Know Macros, Part 1

Malina Tran
Tech and the City
Published in
2 min readJul 22, 2016

Ihave used the term “macros” before, and read about it extensively without fully understanding the terminology. Now, let’s take the time to break it down!

Clojure is a homoiconic language — meaning it has a relationship between source code, data, and evaluation. The greater implication of this is that homoiconic languages enable you to use your code as a set of data structures that can be programmatically manipulated.

Code represents abstract syntax trees using lists, and you’re actually writing textual representations of lists when you write Clojure code. Once the code is written (and representative of data structures), the evaluator comes in to consume the data structure. The evaluator can be thought of as a function that takes a data structure as an argument, processes it based on rules around its type, and returns a result. This process allows us to programmatically modify the program.

Take a look at how code is read and evaluated (diagram from Clojure for the Brave and True):

And now here’s a diagram showing how a macro works:

Macros makes modifying and manipulating fairly easy. For example, if I wanted Clojure to be able to read infix notation (as in `1 + 1`, instead of `+ 1 1`), here is what the code would look like:

(eval
(let [infix (read-string "(1 + 1)")]
(list (second infix) (first infix) (last infix))))
; => 2

This sets `infix` to read the string and convert the string into a list, before calling `eval` on it to be evaluated.

Unfortunately, it’s rather clunky. To resolve this, we can use macros which gives us a convenient way to manipulate lists before Clojure evaluates them. Here are some key aspects of macros:

  1. Unlike a function, when a macro is called, the arguments passed in are not evaluated. Symbols are not resolved and lists are not evaluated. Rather, the unevaluated list data structure is passed in.
  2. Data structure returned by a macro is evaluated. Macro expansion is the process of determining the return value of a macro. In fact, you can use the function `macroexpand` to see what data structure a macro returns before it is evaluated.
(defmacro infix
[infixed]
(list (second infixed)
(first infixed)
(last infixed)))

(infix (1 + 1))
; => 2

So, why does this all matter? Macros allow you to transform an arbitrary data structure like our infamous example, `1 + 1`, into one that can Clojure can evaluate, `+ 1 1`. You’re using Clojure to expand its own syntax!

--

--