Clojure as any other LISP language is homoiconic: the code written in the language is encoded as data structures that the language has tools to manipulate.

The most powerful tool to manipulate code is the macro.

We all know how to call macros in our clojure code. But have you ever asked yourself:

What is exactly the difference between a macro and a function?

or:

What would it take to replace macro calls with function calls in a clojure program?

In this article, we will try to give an answer by stating two assertions and demonstrating them with live code in KLIPSE.

Mountain

Macros vs. functions

One common explanation of the difference between functions and macros is:

  • a function transforms values into other values.
  • a macro transforms code into other code.

We want to give a more precise answer, analysing two aspects of functions and macros:

  1. when the arguments are evaluated?
  2. is the return value evaluated?

Regarding to those two aspects, here are the differences between macros and functions:


  arguments evaluation return value evaluated?
functions before function code execution not evaluated
macros only when macro code evaluates them explicitly evaluated


The proof for functions

About functions, we stated that:

  1. function arguments (the input) are evaluated before the function code execution
  2. function return value (the output) is not evaluated

Let’s check it by writing a function with different side effects in the input and the output. We’ll see what side effect is executed.

We will modify the color and the border of the klispe container as side effects, with a few helpers:

(ns my.m$macros)
(defn set-color! [elem c]
  (set! (.. elem -style -color) c))

(defn set-border [elem border]
  (set! (.. elem -style -border) border))

(defn reset-elem! [elem]
  (set! (.-innerHTML elem) "This is a sentence")
  (set! (.-style elem) nil))

(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.)

Now, let’s check what happens when we call a function:

(defn color-me-fn [arg]
  (list 'set-border 'js/klipse-container "solid 3px red"))

(reset-elem! js/klipse-container)
(color-me-fn (set-color! js/klipse-container "red"))

As you can see above, the color of the text is red but the border is un-mondified: it means that the input is evaluated and the output is not evaluated; the output is returned, but it’s not evaluated.

Q.E.D.∎

The proof for macros

About macros, we stated that:

  1. macros arguments (the input) are not evaluated before the macro code evaluates them explicitly
  2. macros return value (the output) is evaluated

Let’s check it by writing a macro with different side effects in the input and the output. The macro will not evaluate the input and we’ll see what side effect is executed.

(defmacro color-me-macro [arg]
  (list 'set-border 'js/klipse-container "solid 3px red"))

(reset-elem! js/klipse-container)
(my.m/color-me-macro (set-color! js/klipse-container "red"))

As you can see above, the color of the text is un-modified but the border is red: it means that the input is not evaluated and the output is evaluated.

Q.E.D.∎

Two assertions about functions and macros

Now we will try to see how functions and macros are interchangeable.

Let’s say we have a function foo-f and a macro foo-m with exactly the same code. Then, the following two assertions hold:

Assertion #1: the macro expansion is equivalent to the function execution with arguments quoted

(= (macroexpand-1 '(foo-m arg1 arg2 ...))
   (foo-f 'arg1 'arg2 '...))

Assertion #2: the macro execution is equivalent to the evaluation of the function execution with arguments quoted

(= (foo-m arg1 arg2 ...)
   (eval (foo-f 'arg1 'arg2 '...)))

First assertion - macro expansion

The first assertion states that the macro expansion is equivalent to the function execution with arguments quoted.

(= (macroexpand-1 '(foo-m arg1 arg2 ...))
   (foo-f 'arg1 'arg2 '...))

Let’s check it with KLIPSE, using the simple when macro as defined in core.clj:

Second assertion - function evaluation

The second assertion states that the macro execution is equivalent to the evaluation of the function execution with arguments quoted.

(= (foo-m arg1 arg2 ...)
   (eval (foo-f 'arg1 'arg2 '...)))

In order to check the second assertion, we have to define eval in clojurescript: this is easily done with self-host function eval-str:

(You can read more about self-host clojurescript in Self-Hosted Clojurescript in action - Part 1.)

After having illustrated the differences between macros and functions, we will show step-by-step how to write the disp macro that we use so much in our blog (including in this article). Hopefully, it will illustrate and clarify all the basic building blocks of macros in clojure, namely:

  • regular quote
  • syntax quote
  • fully-qualified symbols
  • unquote
  • unique symbol generation
  • quote splicing

Follow us on twitter to get notified, when the next article in the macro tutorial series is published.

Clojure rocks!