Chapter 8. Macros

If you give someone Fortran, he has Fortran. If you give someone Lisp, he has any language he pleases.

Guy Steele

 

This chapter covers

 

Macros are where the rubber of “code is data” meets the road of making programs simpler and cleaner. To fully understand macros, you need to understand the different times of Clojure, of which macros perform the bulk of their work at compile time. We’ll start by looking at what it means for code to be data and data to be used as code. This is the background you’ll need to understand that control structures in Clojure are built out of macros, and how you can build your own. The mechanics of macros are relatively simple, and before you’re halfway through this chapter you’ll have learned all you technically need to write your own. Where macros get complicated is when you try to bring theoretical knowledge of them into the real world, so to help you combat that we’ll lead you on a tour of practical applications of macros.

What kinds of problems do macros solve? To start answering that question, consider Clojure’s -> and ->> macros that return the result of a number of threaded forms. To understand both versions of the arrow macros, we find it useful to think of them as an arrow indicating the flow of data from one function to another—the form (-> 25 Math/sqrt int list) can be read as

  1. Take the value 25.
  2. Feed it into the method Math/sqrt.
  3. Feed that result into the function int.
  4. Feed that result into the function list. Graphically, this can be viewed as shown in figure 8.1.
Figure 8.1. Arrow macro: each expression is inserted into the following one at compile time, allowing you to write the whole expression inside-out when that feels more natural.

It expands into the following expression:

(list (int (Math/sqrt 25)))

When viewed this way, the -> macro can be said to thread a sequence of forms into each in turn. This threading can be done within any form and is always stitched in as the first argument to the outermost expression. On the other hand, the ->> macro will thread the form as the last argument. Observe how the placement of commas[1] works as visual markers for the stitch point:

1 Because commas are considered whitespace. The use here is instructive and not meant as idiomatic.

(-> (/ 144 12) (/ ,,, 2 3) str keyword list)
;=> (:2)

(-> (/ 144 12) (* ,,, 4 (/ 2 3)) str keyword (list ,,, :33))
;=> (:32 :33)

(->> a (+ 5 ,,,) (let [a 5] ,,,))
;=> 10

Using the arrows macro is useful when many sequential operations need to be applied to a single object. So this is one potential use case for macros: taking one form of an expression and transforming it into another form. In this chapter, we’ll also look at using macros to combine forms, change forms, control evaluation and resolution of arguments, manage resources, and build functions. But first, what does it mean that Clojure code is data, and why should you care?

8.1. Data is code is data

You’re already familiar with textual representations of data in your programs, at least with strings, lists, vectors, maps, and so on. Clojure, like other Lisps, takes this one step further by having programs be made entirely out of data. Function definitions in Clojure programs are also represented using an aggregation of the various data structures mentioned in the previous chapters. Likewise, the expressions representing the execution of functions and the use of control structures are also data structures! These data representations of functions and their executions represent a concept different from the way other programming languages operate. Typically, there’s a sharp distinction between data structures and functions of the language. In fact, most programming languages don’t even remotely describe the form of functions in their textual representations. With Clojure, there’s no distinction between the textual form and the actual form of a program. When a program is the data that composes the program, then you can write programs to write programs. This may seem like nonsense now, but as you’ll see throughout this chapter, it’s powerful.

To start with, look at the built-in Clojure function eval, whose purpose is to take a data structure representing a Clojure expression, evaluate it, and return the result. This behavior can be seen in the following examples:

(eval 42)
;=> 42

(eval '(list 1 2))
;=> (1 2)

(eval (list 1 2))
; java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.
     lang.IFn

Why did we get an exception for the last example? The answer to that lies in the previous example. The quote in '(list 1 2) causes eval to view it as (list 1 2), which is the function call to create the resulting list. Likewise, for the final example eval received a list of (1 2) and attempted to use 1 as a function, thus failing. Not very exciting, is it? The excitement inherent in eval stems from something that we mentioned[2] earlier—if you provide eval a list in the form expected of a function call, then something else should happen. This something else would be the evaluation of a function call and not of the data structure itself. Look at what happens when we try evaluating something more complicated:

2 All the way back in section 2.5.

(eval (list (symbol "+") 1 2))
;=> 3

In words, the steps involved were as follows:

  1. The function symbol received a string + and returned a symbol data type of +.
  2. The function list received three arguments: a symbol +, the integer 1, and the integer 2, and returned a list of these elements.
  3. The eval function received a list data type of (+ 1 2), recognized it as the function call form, and executed the + function with the arguments 1 and 2, returning the integer 3.

8.1.1. Syntax-quote, unquote, and splicing

Listing 8.1. An implementation of eval taking a local context

 

Handling nested syntax-quotes

Dealing with nested syntax-quotes can at times be complicated. But you can visualize the way in which unquoting affects the nested structures as result of repeated evaluations (Steele 1990) relative to its nesting level:

(let [x 9, y '(- x)]
  (println `y)
  (println ``y)
  (println ``~y)
  (println ``~~y)
  (contextual-eval {'x 36} ``~~y))
; user/y
; (quote user/y)
; user/y
; (- x)
;=> -36

The nesting of the syntax-quotes in the first two println calls takes the value of y further up the abstraction ladder. But by including a single unquote in the third println, we again bring it back down. Finally, by unquoting a second time, we’ve created a structure that can then be evaluated—and doing so yields the result -36. We had to use contextual-eval in the tail because core eval doesn’t have access to local bindings—only Var bindings. One final note is that had we attempted to unquote one extra time, we’d have seen the exception java.lang.IllegalStateException: Var clojure.core/unquote is unbound. The reason for this error is that unquote is the way to “jump” out of a syntax-quote, and to do so more than nesting allows will cause an error. We won’t use this technique in this chapter, and in most cases you won’t need to utilize it unless you’re planning to create macro-defining macros—something we won’t do until section 13.1.

 

In section 1.5.6, we mentioned quoting and its effects on evaluation, and in this chapter we’ll expand on that theme fully as it relates to Clojure’s macro facility. But the functionality of the quoting forms is orthogonal to macros, and they can be used independently. As we show[3] in listing 8.1, using quoting and unquoting in a function allows us to create an evaluation function, contextual-eval, that takes an explicit context map. Rarely will you see the use of syntax-quote outside the body of a macro, but there’s nothing preventing it from being used this way—and doing so is powerful. But the maximum power of quoting forms is fully realized when used with macros.

3 Thanks to George Jahad for the implementation on which contextual-eval is based.

Working from a model where code is data, Clojure is able to manipulate structures into different executable forms at both runtime and compile time. We’ve already shown how this can be done at runtime using eval and contextual-eval, but this doesn’t serve the purpose of compile-time manipulation. It probably doesn’t need saying, but because this is a book about Clojure, we will: macros are the way to achieve this effect.

8.1.2. Macro rules of thumb

Before we begin, we should list a few rules of thumb to observe when writing macros:

Throughout this chapter, you’ll see all of these rules to varying degrees. Obviously, we’re trying to balance best practices, teaching, and page counts, so we may not always adhere entirely. Even so, we’ll try to highlight those times when we do break from the recommended heuristics. Having said that, we’ll talk first about the most ubiquitous use of macros: creating custom control structures.

8.2. Defining control structures

Most control structures in Clojure are implemented via macros, so they provide a nice starting point for learning how macros can be useful. Macros can be built with or without using syntax-quote, so we’ll show examples of each.

In languages lacking macros, such as Haskell[5] for example, the definition of control structures relies on the use of higher-order functions such as we showed in section 7.1.2. Though this fact in no way limits the ability to create control structures in Haskell, the approach that Lisps take to the problem is different. The most obvious advantage of macros over higher-order functions is that the former manipulate compile-time forms, transforming them into runtime forms. This allows your programs to be written in ways natural to your problem domain, while still maintaining runtime efficiency. Clojure already provides a rich set of control structures, including but not limited to doseq, while, if, if-let, and do, but in this section we’ll write a few others.

5 Although there’s a GHC extension named Template Haskell that provides a macro-like capability, this isn’t the norm.

8.2.1. Defining control structures without syntax-quote

Because the arguments to defmacro aren’t evaluated before being passed to the macro, they can be viewed as pure data structures, and manipulated and analyzed as such. Because of this, amazing things can be done on the raw forms supplied to macros even in the absence of unquoting.

Imagine a macro named do-until that will execute all of its clauses evaluating as true until it gets one that is falsey:

(do-until
  (even? 2) (println "Even")
  (odd?  3) (println "Odd")
  (zero? 1) (println "You never see me")
  :lollipop (println "Truthy thing"))
; Even
; Odd
;=> nil

A good example of this type of macro is Clojure’s core macro cond, which with some minor modifications can be made to behave differently:

(defmacro do-until [& clauses]
  (when clauses
    (list `when (first clauses)
           (if (next clauses)
             (second clauses)
             (throw (IllegalArgumentException.
                     "do-until requires an even number of forms")))
           (cons 'do-until (nnext clauses)))))

The first expansion of do-until illustrates how this macro operates:

(macroexpand-1 '(do-until true (prn 1) false (prn 2)))
;=> (when true (prn 1) (do-until false (prn 2)))

do-until recursively expands into a series of when calls, which themselves expand into a series of if expressions:

(require '[clojure.walk :as walk])
(walk/macroexpand-all '(do-until true (prn 1) false (prn 2)))
;=> (if true (do (prn 1) (if false (do (prn 2) nil))))

(do-until true (prn 1) false (prn 2))
; 1
;=> nil

Now you could write out the nested if structure manually and achieve the same result, but the beauty of macros lies in the fact that they can do so on your behalf while presenting a lightweight and intuitive form. In cases where do-until can be used, it removes the need to write and maintain superfluous boilerplate code. This idea can be extended to macros in general and their propensity to reduce unneeded boilerplate for a large category of circumstances, as the programmer desires. One thing to note about do-until is that it’s meant to be used only for side effects, because it’s designed to always return nil. Macros starting with do tend to act the same.

8.2.2. Defining control structures using syntax-quote and unquoting

Not all control structures will be as simple as do-until. Instead, there will be times when you’ll want to selectively evaluate macro arguments, structures, or substructures. In this section, we’ll explore one such macro named unless, implemented using unquote and unquote-splice.

Ruby provides a control structure named unless that reverses the sense (Olsen 2007) of a when statement, executing the body of a block when a given condition evaluates to false:

(unless (even? 3) "Now we see it...")
;=> "Now we see it..."

(unless (even? 2) "Now we don't.")
;=> nil

The maverick implementation[6] of unless as demonstrated previously and as shown in the following listing is straightforward.

6 The proper way to define unless is either (defmacro unless [& args] `(when-not ~@args)) or even (clojure.contrib.def/defalias unless when-not)—or just use when-not from the start.

Listing 8.2. A Clojure Implementation of unless

The body of the unless implementation uses three features first shown in section 1.5.6: syntax-quote (written as a single back-quote), unquote (written as ~), and unquote-splice (written as ~@). Syntax-quote allows the if form to act as a template for the expression that any use of the macro becomes when expanded. The unquote and splicing-unquote provide the “blanks” where the values for the parameters condition and body will be inserted.

Because unless relies on the result of a condition for its operation, it’s imperative that it evaluate the condition part using unquote. If we didn’t use unquote in this instance, then instead of evaluating a function (even? 3), it would instead attempt to resolve a namespace Var named condition that may not exist—and if it does exist, it might be arbitrarily truthy at the time of the macro call. Some of the unintended consequences of this mistake are shown in the next listing.

Listing 8.3. Name capture in unless

Clearly this isn’t the desired behavior. Instead, by unquoting the condition local, we ensure that the function call is used instead. It’s easy to forget to add an unquote to the body of a macro, and depending on the condition of your runtime environment, the problem may not be immediately obvious.

8.3. Macros combining forms

Macros are often used for combining a number of forms and actions into one consistent view. This behavior could be seen in the previous section with the do-until macro, but it’s more general. In this section, we’ll show how macros can be used to combine a number of tasks in order to simplify an API. Clojure’s defn macro is an instance of this type of macro because it aggregates the processes of creating a function, including

You could perform all of these steps over and over again every time you wanted to create a new function, but thanks to macros you can instead use the more convenient defn form. Regardless of your application domain and its implementation, programming language boilerplate code inevitably occurs. But identifying these repetitive tasks and writing macros to simplify and reduce or eliminate the tedious copy-paste-tweak cycle can work to reduce the incidental complexities inherent in a project. Where macros differ from techniques familiar to proponents of Java’s object-oriented style—including hierarchies, frameworks, inversion of control, and the like—is that they’re treated no differently by the language itself. Clojure macros work to mold the language into the problem space rather than forcing you to mold the problem space into the constructs of the language. There’s a specific term for this, domain-specific language, but in Lisp the distinction between DSL and API is thin to the point of transparency.

Envision a scenario where you want to be able to define Vars that call a function whenever their root bindings change. You could do this using the add-watch function that allows for the attachment of a watcher to a reference type that’s called whenever a change occurs within. The add-watch function itself takes three arguments: a reference, a watch function key, and a watch function called whenever a change occurs. You could enforce that every time someone wants to define a new Var, they must follow these steps:

  1. Define the Var.
  2. Define a function (maybe inline to save a step) that will be the watcher.
  3. Call add-watch with the proper values.

A meager three steps isn’t too cumbersome a task to remember in a handful of uses, but over the course of a large project it’s easy to forget and/or morph one of these steps when the need to perform them many times occurs. Therefore, perhaps a better approach is to define a macro to perform all of these steps for you, as the following definition does:

(defmacro def-watched [name & value]
  `(do
     (def ~name ~@value)
     (add-watch (var ~name)
                :re-bind
                (fn [~'key ~'r old# new#]
                  (println old# " -> " new#)))))

Ignoring symbol resolution and auto-gensym, which we’ll cover in upcoming sections, the macro called as (def-watched x 2) expands into roughly the following:

(do (def x 2)
    (add-watch (var x)
               :re-bind
               (fn [key r old new]
                 (println old " -> " new))))

The results of def-watched are thus

(def-watched x (* 12 12))
x
;=> 144

(def x 0)
; 144 -> 0

Lisp programs in general (and Clojure programs specifically) use macros of this sort to vastly reduce the boilerplate needed to perform common tasks. Throughout this chapter, you’ll see macros that combine forms, so there’s no need to dwell on the matter here. Instead, we’ll move on to a macro domain that does just that, with the added bonus of performing some interesting transformations in the process.

8.4. Using macros to change forms

One way to design macros is to start by writing out example code that you wish worked—code that has the minimal distance between what you must specify and the specific application domain in which you’re working. Then, with the goal of making this code work, you begin writing macros and functions to fill in the missing pieces.

For example, when designing software systems, it’s often useful to identify the “things” comprising your given application domain, including their logical groupings. The level of abstraction at this point in the design is best kept high (Rosenberg 2005) and shouldn’t include details about implementation. Imagine that you want to describe a simple domain of the ongoing struggle between humans and monsters:

Though this is a simple format, it needs work to be programmatically useful. Therefore, the goal of this section is to write macros performing the steps to get from this simple representation to the one more conducive to processing. One such structure is a tree composed of individual generic nodes, each taking a form similar to that shown in the next listing.

Listing 8.4. Domain DSL’s underlying form

You’d never say this is a beautiful format, but it does present practical advantages over the original format—it’s a tree, it’s composed of simple types, it’s regular, and it’s recognizable to some existing libraries.

 

Clojure Aphorism

Clojure is a design language where the conceptual model is also Clojure.

 

We’ll start with the outer-level element, domain:

(defmacro domain [name & body]
  `{:tag :domain,
    :attrs {:name (str '~name)},
    :content [~@body]})

The body of domain is fairly straightforward in that it sets the domain-level tree node and splices the body of the macro into the :content slot. After domain expands, you’d expect its body to be composed of a number of grouping forms, which are then handled by the aptly named macro:

(declare handle-things)

(defmacro grouping [name & body]
  `{:tag :grouping,
    :attrs {:name (str '~name)},
    :content [~@(handle-things body)]})

Similarly to domain, grouping expands into a node with its body spliced into the :con-tent slot. But grouping differs from domain in that it splices in the result of the call to a function handle-things:

(declare grok-attrs grok-props)

(defn handle-things [things]
  (for [t things]
    {:tag :thing,
     :attrs (grok-attrs (take-while (comp not vector?) t))
     :content (if-let [c (grok-props (drop-while (comp not vector?) t))]
                [c]
                [])})))

Because the body of a thing is fairly simple and regular, we can simplify the implementation of handle-things by again splitting it into two functions. The first function grok-attrs handles everything within the body of a thing that’s not a vector, and the other grok-props handles properties that are. In both cases, these leaf-level functions return specifically formed maps:

(defn grok-attrs [attrs]
  (into {:name (str (first attrs))}
        (for [a (rest attrs)]
          (cond
            (list? a) [:isa (str (second a))]
            (string? a) [:comment a]))))

The implementation of grok-attrs may seem overly complex, especially given that the example domain model DSL only allows for a comment attribute and an optional isa specification. But by laying out this way, we can easily expand the function to handle a richer set of attributes later. Likewise with grok-props, we provide a more complicated function to pull apart the vector representing a property so that it’s more conducive to expansion:

(defn grok-props [props]
  (when props
    {:tag :properties, :attrs nil,
     :content (apply vector (for [p props]
                 {:tag :property,
                  :attrs {:name (str (first p))},
                  :content nil}))}))

Now that we’ve created the pieces, take a look at the new DSL in action in the following listing.

Listing 8.5. Exploring the domain DSL results

(:tag d)
;=> :domain

(:tag (first (:content d)))
;=> :grouping

Maybe that’s enough to prove to you that we’ve constructed the promised tree, but probably not. Therefore, we can pass a tree into a function that expects one of that form[7] and see what comes out on the other end:

7 The namespace clojure.contrib.json in the Clojure contrib library also contains some functions that would be able to handle the domain DSL structure seamlessly. Additionally, Enlive (http://mng.bz/8Hh6) should also recognize the resultant structure.

(use '[clojure.xml :as xml])
(xml/emit d)

Performing this function call will print out the corresponding XML representation, minus the pretty printing, shown here.

Listing 8.6. An XML transformation of the domain DSL structure

Our approach was to define a single macro entry point domain, intended to build the top-level layers of the output data structure and instead pass the remainder on to auxiliary functions for further processing. In this way, the body of the macro expands into a series of function calls, each taking some subset of the remaining structure and returning some result that’s spliced into the final result. This functional composition approach is fairly common when defining macros. The entirety of the domain description could’ve been written within one monolithic macro, but by splitting the responsibilities, you can more easily extend the representations for the constituent parts.

Macros take data and return data, always. It so happens that in Clojure, code is data and data is code.

8.5. Using macros to control symbolic resolution time

Whereas functions accept and return values that are meaningful to your application at runtime, macros accept and return code forms that are meaningful at compile time. Any symbol has some subtleties depending on whether it’s fully qualified or not, its resolution time, and its lexical context. These factors can be controlled in any particular case by the appropriate use of quoting and unquoting, which we explore in this section.

Clojure macros are mostly safe from name capture, in that the use of syntax-quote in macros is encouraged and idiomatic, and it’ll resolve symbols at macro-expansion time. This strategy reduces complexity by ensuring that symbols refer to those available at a known instance rather than to those unknown in the execution context.

For example, consider one of the simplest possible macros:

(defmacro resolution [] `x)

Viewing the expansion of this macro is illuminating in understanding how Clojure macros resolve symbols:

(macroexpand '(resolution))
;=> user/x

The expansion of the macro resolves the namespace of the syntax-quoted symbol x. This behavior is useful in Clojure by helping to avoid free name capturing problems that are possible in a macro system such as that found in Common Lisp.[8] Here’s an example that would trip up a lesser implementation of syntax-quote, but which does just what we want in Clojure:

8 Among one of the ways that Common Lisp works to alleviate this kind of problem is the use of gensym. The key difference is that in Common Lisp, you have to be careful to avoid name capturing, whereas Clojure avoids it by default.

(def x 9)
(let [x 109] (resolution))
;=> 9

The x defined in the let isn’t the same as the namespace-qualified user/x referred to by the macro resolution. As you might expect, the macro would’ve thrown an unbound Var exception had we not first executed the call to def.

Clojure does provide a way to defer symbolic resolution for those instances where it may be useful to resolve it within the execution context, which we’ll show now.

8.5.1. Anaphora

Anaphora[9] in spoken language is a term used in a sentence referring back to a previously identified subject or object. It helps to reduce repetition in a phrase by replacing “Jim bought 6,000 Christmas lights and hung all of the Christmas lights,” with “Jim bought 6,000 Christmas lights and hung them all.” In this case, the word them is the anaphora. Some programming languages use anaphora, or allow for their simple definition. Scala has a rich set of anaphoric (Odersky 2008) patterns primarily focused around its _ operator:

9 Anaphora is pronounced un-NAF-er-uh.

Array(1, 2, 3, 4, 5).map(2 * _)
//=> res0: Array[Int] = Array(2, 4, 6, 8, 10)

In this Scala example, the underscore serves to refer back to an implicitly passed argument to the map function, which in this case would be each element of the array in succession. The same expression could be written with (x) => 2 * x—the syntax for an anonymous function—in the body of the map call, but that would be unnecessarily verbose.

Anaphora don’t nest, and as a result are generally not employed in Clojure. Within a nested structure of anaphoric macros, you can only refer to the most immediate anaphoric binding, and never those from outer lexical contours, as demonstrated in listing 8.7. For example, the Arc programming language (Graham Arc) contains a macro named awhen similar to Clojure’s when, save that it implicitly defines a local named it used within its body to refer to the value of the checked expression.

Listing 8.7. An example of anaphora and its weakness

Clojure provides similar macros that do nest and replace the need for anaphora: if-let and when-let. When designing your own macros, it’s preferred that you build them along these lines so that the macro itself takes the name to be bound. But just because typical anaphorics are limited, that’s not to say that they’re entirely useless. Instead, for your own libraries you may find that their usage is intuitive. You’ll see the pattern ~'symbol at times in Clojure macros, because this is the idiomatic way to selectively capture a symbolic name within the body of a macro. The reason for this bit of awkwardness[10] is because Clojure’s syntax-quote attempts to resolve symbols in the current context, resulting in fully qualified symbols. Therefore, ~' avoids that resolution by unquoting a quote.

10Awkwardness is good since it’s a strong signal to make the user aware he is drifting away from the true path to clojure enlightenment.—Christophe Grand

8.5.2. (Arguably) useful selective name capturing

We contend that there’s only one case to be made for selective name capturing in Clojure macros—the case when you’re forced to embed third-party macros and functions in your macros that rely on the existence of anaphora. One such macro is the proxy macro in Clojure’s core libraries, which provides an anaphoric symbol named this within its body for use therein. We’ll cover the proxy macro in depth in section 9.1, so there’s no need to discuss it here. But bear in mind that should this macro ever be embedded within your own macros, you may be forced to use the ~'this pattern.

 

Hygiene

A hygienic macro is one that doesn’t cause name capturing at macro expansion time. Clojure macros help to ensure hygiene by namespace-resolving symbols within the body of syntax-quote at macro-definition time. As you saw, symbols are expanded into the form user/a-symbol within the body of syntax-quote. To close this hygienic loop, Clojure also disallows the definition of qualified locals within the body of a macro. In order to selectively capture names within Clojure macros, you must explicitly do so via the ~'a-symbol pattern.

 

Clojure prefers that symbols be either declared or bound at macro-definition time. But using the resolution deferment strategy outlined earlier, you can relax this requirement for those instances where doing so would be useful.

8.6. Using macros to manage resources

Managing scarce resources or those with a finite lifetime is often viewed as a sweet spot for macro usage. In Java, such activities are almost always performed using the try/catch/finally idiom (Bloch 2008), as shown:

try {
     // open the resource
}
catch (Exception e) {
     // handle any errors
}
finally {
// in any case, release the resource
}

We showed in section 1.5.8 that Clojure also has a try/catch/finally form that can be used in the same way, but like the Java idiom, you must remember to explicitly close the resource within the finally block. Clojure provides a generic with-open macro, demonstrated in listing 8.8, that when given a “closeable” object bound to a name, will automatically call its .close method (assuming that one exists) within a finally block.

Listing 8.8. An example of with-open

Not all instances of resources in your own programs will be closeable. In these instances, we present a generic template for resource allocating macros that can be used for many cases, shown in the following listing.

Listing 8.9. A more general template for with-open-like macros
(defmacro with-resource [binding close-fn & body]
  `(let ~binding
     (try
       (do ~@body)
        (finally
        (~close-fn ~(binding 0))))))

(let [stream (joc-www)]
  (with-resource [page stream]
     #(.close %)
    (.readLine page)))

The macro with-resource is generic enough and so generally ubiquitous across differing flavors (Symbolics Inc.[11]) to almost be considered a Lisp design pattern. The macro with-resource differs from with-open in that it does not assume that its resource is closeable but instead delegates the task of closing the resource to a close-fn function taken as an argument. One final point is that with-resource avoids the nesting problem of anaphoric macros because it requires that the resource be named explicitly a la [stream (joc-www)]. This approach allows for the proper nesting of with-resource macros; and in fact, the use of named bindings marked by vectors is ubiquitous and idiomatic in Clojure.

11 The spirit of this section was inspired by a similar discussion of “Writing Macros to Surround Code.” If you can get your hands on the original Symbolics manuals, do so—they contain a wealth of information.

8.7. Putting it all together: macros returning functions

In section 7.1, we introduced Clojure’s constraint facility that uses pre- and postcondition checks on function arguments and return values respectively to ensure some assertions about said function. In that section, we talked briefly about how separating the constraints from the functions they’re constraining allows you to more flexibly apply different assertion templates based on need and context.

 

Clojure Aphorism

Clojure programmers don’t write their apps in Clojure. They write the language that they use to write their apps in Clojure.

 

In this section, we’re going to take this idea one step further by introducing a macro named contract that implements a simple DSL to describe function constraints. For example, a proposed DSL should be nameable and describe its pre- and postconditions in an intuitive way, building a higher-order function that will be used to apply its constraints later. The following sketches a contract specifying that a function should take only a positive number and return its value multiplied by 2:

(contract doubler
  [x]
  (:require
    (pos? x))
  (:ensure
    (= (* 2 x) %)))

The contract’s :require list (Meyer 2000) refers to preconditions, and the :ensure list the postconditions. Given this description, how would you start to implement a macro to realize this sketch? If you haven’t already gathered from the section title and the initial problem statement, the macro must return a function, so we’ll start there with the following listing.

Listing 8.10. The contract top-level macro
(declare collect-bodies)

(defmacro contract [name & forms]
  (list* `fn name (collect-bodies forms)))

The contract macro calls a function collect-bodies that hasn’t been written yet, so we had to use declare to avoid a compilation error. Hold fast, because we’re going to implement that necessary function soon. But first, imagine what the form of the returned function will be when it finally comes out of contract:

(fn doubler
  ([f x]
     {:post [(= (* 2 x) %)],
      :pre [(pos? x)]}
     (f x)))

We also want to allow for the multi-arity function definition form so that the contract can take more than one specification per arity function, each separated by a vector of symbols. The first step down that path starts with an implementation of collect-bodies:

(declare build-contract)

(defn collect-bodies [forms]
  (for [form (partition 3 forms)]
    (build-contract form)))

The primary task of collect-bodies is to build a list of the body portion of the contract, each partitioned into three segments. These partitions represent the arg-list, requires, and ensures of the contract, which we’ll then pass along to another function named build-contract, that will build the arity bodies and corresponding constraint maps. This is shown next.

Listing 8.11. The contract auxiliary function build-contract

The function build-contract is where the heart of contract construction lies, building the arity bodies that contain constraint maps. The difference is that each body is a higher-order function that takes an additional function as an argument, which the arguments are then delegated to. This allows us to compose the contract function with a constrained function, as shown in the next listing.

Listing 8.12. Composition of contract function and constrained function

As you might expect, times2 fulfills the contract, whereas times3 doesn’t. We could extend doubler-contract to handle extended arities, as shown here.

Listing 8.13. Contract for multiple-arity functions

We could extend the contract to cover any number of expected function arities using contract, independent of the functions themselves. This provides a nice separation of the work to be done from the expected work to be done. By using the contract macro, we’ve provided a way to describe the expectations of a function, including but not limited to

The contract macro could be extended in many complementary ways. For example, Clojure’s function constraints are verified using logical and—the implications being that any additional pre- or postcondition works to tighten the requirements. But there may be times when loosening the constraints on the inputs and tightening them on the output makes more sense. In any case, this section isn’t about the nuances of contracts programming, and to dig deeper would elude the point that using macros to return functions is an extremely powerful way to extend the capabilities of Clojure itself.

8.8. Summary

We’ve explored various use cases for macros and given examples of each. Though instructive to the point under discussion, we also tried to show how macros can be used to mold Clojure into the language that shortens the gap between your problem space and solution space. In your own unique programs, you should try to do the same. But the most important skill that you can learn on your path toward macro mastery is the ability to recognize when to avoid using them. The general answer of course is whenever, and as often as you can.

In the next chapter, we’ll cover various powerful way to organize and categorize data types and functions using Clojure’s namespaces, multimethods, types, and protocols.