After having understood the fundamental difference between functions and macros, we are now ready to write our first macro.

In this article, we want to write macros without the help of idiomatic tools that clojure provides for writing macros (syntax quote, unquote, gensym etc…). The purpose of this article it to let you understand why those powerful tools are mandatory, by experiencing how it feels to write a macro without those tools.

We will train ourselves with a simple macro that we use a lot for our blog posts: the disp macro.

Kind of eating our own dog food

Dog

Definition of the disp macro

The disp macro receives expressions and returns a string with the expressions and their respective evaluations.

For instance:

(disp (map inc [1 2 3]) (str "hello" " " "world") (true? 1)) 
; (map inc [1 2 3]) => (2 3 4)
; (str "hello" " " "world") => "hello world"
; (true? 1) => false

In this article, for the sake of simplicity, we will limit ourselves to the case of a single expression.

Let’s start coding!

There are three parts in the disp macro:

  1. expression quoting (keeping is at it is)
  2. expression evaluation (evaluating it)
  3. concatenation of the quoting and the evaluation

Playground

This namespace is going to be our playground for dealing with macros:

(ns my.repl$macros)

(If you wonder why we have to append $macros to the namespace and to reference the fully-qualified macro with self-hosted clojurescript, read Messing with Macros at the REPL.)

How to quote inside a macro

First, we need to understand how to quote an expression inside a macro. Let’s try to write a macro that receives an expression and return it, quoted.

(defmacro my-quote [form]
  (quote form))

It doesn’t work.

  (my.repl/my-quote (map inc [1 2 3]))

The reason is that - as you can see with macroexpand-1 - (quote form) is form.

  (macroexpand-1 '(my.repl/my-quote (map inc [1 2 3])))

Let’s try to state explicitly what is the requirement for the my-quote macro: We need to return an expression that when it is evaluated, it becomes (map inc [1 2 3]). The expression that fits this definition is (quote (map inc [1 2 3])). In other words, it is a list whose first element is the symbol quote and its second element is (map inc [1 2 3]).

Now, try to update the klipse above with your correct implementation for the my-quote macro.

(Even on your mobile device, it works: just wait 3 seconds and your code is automatically evaluated.)

If you cannot make it after a couple of trials, you can read my solution below…





Give it another try, before surrending…

:)

;)




Here is my solution:

(defmacro my-quote-fixed [form]
  (list 'quote form))

Now, it works:

  (my.repl/my-quote-fixed (map inc [1 2 3]))

How to concatenate evaluations inside a macro

Now, we have to figure out how to concatenate evaluations inside a macro. I hope that the following example will clarify what I mean by that.

(concat-evaluations "hello" (+ 1 2))
;"hello 3"

Let’s give it a try:

(defmacro concat-evaluations [a b]
  (str a " " b))

It seems to work:

  (my.repl/concat-evaluations "hello" "world")

But in reality, it fails:

  (my.repl/concat-evaluations "hello" (+ 1 2))

It fails because inside the macro, the value of b is (+ 1 2), so the str function appends (+ 1 2) to “hello”. Like for my-quote, the idea is to return an expression that when evaluated it becomes (str "hello" " " 3).

Try to find a solution of your own.

My solution is:

(defmacro concat-evaluations-fixed [a b]
  (list 'str a " " b))

And it works:

  (my.repl/concat-evaluations-fixed "hello" (+ 1 2))

If you are curious, you can take a look at the expansion of the macro:

  (macroexpand-1 '(my.repl/concat-evaluations-fixed "hello" (+ 1 2)))

Implementation of the disp macro

Now we are ready to implement the disp macro, by assembling our building blocks:

(defmacro disp [form]
  (list 'str (list 'quote form) " => " form))
  (my.repl/disp (map inc [1 2 3]))
  (macroexpand-1 '(my.repl/disp (map inc [1 2 3])))

Really simple right?

Issues

There are a couple of issues with our naive implementation:

A. It’s really complicated to write macros this way: almost everything has to be embedded into a expression that begins with list.

B. The code generated by the macro doesn’t look like the code of the macro: in the generated code, list doesn’t occur at all!

C. What happens if you define local variables inside a macro? They might conflict with the variables defined in the scope of the macro caller. Here is an illustration of this issue:

(defmacro concat-evaluations-sep [a b]
  (list 'let ['sep ": "]
          (list 'str a 'sep b)))
  (my.repl/concat-evaluations-sep "hello" (map inc [100 2 3]))

So far so good…

But now??!?

(def sep 100)
(my.repl/concat-evaluations-sep "hello" (map inc [sep 2 3]))
  (macroexpand-1 '(my.repl/concat-evaluations-sep "hello" (map inc [sep 2 3])))

D. Your macro will behave completely crazy when it is called inside a namespace where one of the functions that you use in the code of the macro is overriden.

Here is an example:

Next steps

The 4 issues mentioned above are solved elegantly in clojure with the idiomatic tools I mentioned in the introduction. Here is a presentation of syntax quote.

After reading this article, you should be able to re-write the disp macro, idiomatically.

Please share your best implementations in the comments below.

Clojure rocks!