Chapter 7. Evolving Clojure through macros

 

This chapter covers

  • Introduction to macros
  • Macro examples from within Clojure
  • Writing your own macros

 

Macros are the most distinguishing feature of Clojure when compared to languages such as Java and Ruby. Macros make possible things that can only be dreamed of in other languages. The macro system is why Lisp is known as the programmable programming language, and you’ll see how you can grow your own language on top of Clojure. Macros are a useful ingredient in bottom-up programming, the approach where an application is written by first modeling low-level entities in a domain and then combining them to create complex ones. Understanding and using macros well is the key to becoming a master Clojure programmer.

If you talk to seasoned Lisp or Clojure programmers, you’ll find that opinion about the use of macros varies a great deal. Some say that macros are too powerful and that they should be used with great caution. I’ve always thought that any feature of a programming language can be misused when it isn’t fully understood. Further, the advantages of using macros far outweigh the perceived disadvantages. After all, the whole point of Clojure being homoiconic is to make the macro system possible.

This chapter discusses macros are and how to use them. We’ll begin by writing an example macro, which will help you explore Clojure’s macro-writing facilities. Then, we’ll dig into the Clojure source code to examine a few well-written macros. It’s inspiring to learn that parts of Clojure itself are written as macros and that you can use this facility in your own programs. Finally, you’ll write a few macros of your own. We’ll begin with explaining what a macro is and why a language might need a macro system.

7.1. Macro basics

In order to explain what a macro is, we’ll take a step back and examine language runtimes again. Recall from chapter 1 that the Clojure runtime processes source code differently when compared to most other languages. Specifically, there’s a read phase followed by an evaluation phase. In the first phase, the Clojure reader converts a stream of characters (the source code) into Clojure data structures. These data structures are then evaluated to execute the program. The trick that makes macros possible is that Clojure offers a hook between the two phases, allowing the programmer to process the data structures representing the code before they’re evaluated. Figure 7.1, which you also saw in chapter 1, illustrates these phases.

Figure 7.1. Phases of the Clojure runtime. This separation is what makes the macro system possible.

Code is converted into data structures and these data structures are then evaluated. Macros are functions that the programmer can write that act upon these data structures before they’re evaluated. Macros allow code to be modified programmatically before evaluation, making it possible to create whole new kinds of abstractions. Macros operate at the syntactic level by operating on the code itself. Consequently, you can use them to add features to the Clojure language itself. You’ll see examples of this in this chapter.

7.1.1. Textual substitution

As an example, imagine that you had a ref called a-ref:

(def a-ref (ref 0))

Now, imagine that you wanted to change the value of a-ref to 1. You might do something like this:

(dosync
  (ref-set a-ref 1))

Remember that code is data, which means that this code snippet is just a list containing symbols and other lists—the first one being dosync, followed by a nested list where the symbols are ref-set, a-ref, and 1.

Even if your program used only the single ref shown here, the need to wrap every call to ref-set in a dosync would quickly become tedious. In the real world, you could use an atom, but using a ref is acceptable for the purposes of this example. You could write something called sync-set that wouldn’t need a dosync when called and then would do what ref-set does.

You could implement this using a macro called sync-set that manipulates the code as data to insert the required dosync in the appropriate place. The following call would be transformed into the previous one:

(sync-set a-ref 1)

Let’s write the macro. Recall that new lists can be created using the list function and that things can be quoted using the ' macro character.

(defmacro sync-set [r v]
  (list 'dosync
    (list 'ref-set r v)))

A macro definition looks like a function definition. Internally, macros are functions, tagged as macros via metadata. The difference between functions and macros is that functions execute to return a value, whereas macros execute to return s-expressions that in turn are evaluated to return a value.

An important point to note is that macros operate well before evaluation time and have no notion of what values might be passed in as arguments later on. For instance, you couldn’t dereference a-ref and output different kinds of s-expressions depending on the value, because during macro expansion time there’s no ref, just the symbols r and v. Macros operate on symbols directly, and this is why they’re useful for symbolic manipulation of code.

All this might seem a bit much to achieve the functionality provided by sync-set, because it would be trivial to write it as a function instead. In fact, in the real world, you would indeed write it as a function. Now you know what macros do: they transform or generate arbitrary s-expressions. We’ll now look at something macros can do that functions can’t.

7.1.2. The unless example

Since the book The C Programming Language came out, almost all programming language books have used the “Hello, world!” program as an introductory example. There’s a similar tradition when it comes to explaining macros, and it involves adding the unless control structure to the language. unless is kind of the opposite of the if form. Here’s the general if form, as a reminder:

(if test then else)

If the test expression returns true, the then expression is evaluated. The optional else expression will be evaluated if the test returns false. Here’s an example:

(defn exhibits-oddity? [x]
  (if (odd? x)
    (println "Very odd!")))

The Ruby programming language provides an unless form, which is also a conditional that can be used in similar functions. Clojure doesn’t provide unless, but if it were there, it might work as follows:

(defn exhibits-oddity? [x]
  (unless (even? x)
    (println "Very odd, indeed!")))

Obviously, trying this won’t work in Clojure because it will complain that it’s unable to resolve the symbol unless. Our first attempt at fixing this error will involve writing a function.

The unless function

Let’s define a function that implements unless:

(defn unless [test then]
  (if (not test)
    then))

After defining unless as shown here, the definition of exhibits-oddity? will work without a problem. It will even work correctly, as is evident if you test it at the REPL by calling it with an odd number like 11:

user=> (exhibits-oddity? 11)
Very odd, indeed!
nil

Trouble arises when it’s tested with an even number, such as 10:

user=> (exhibits-oddity? 10)
Very odd, indeed!
nil

It appears that exhibits-oddity? declares all numbers as odd. The reason for this is that unless is a function, and all functions execute according to the following rules:

  1. Evaluate all arguments passed to the function call form.
  2. Evaluate the function using the values of the arguments.

Step 1 causes the arguments to be evaluated. In the case of our unless function, that’s the test and then expressions. This happens before execution of the if form even begins. Because all functions follow these rules, there’s no way that you can use a function to implement your desired functionality for unless. No matter what you try, the arguments would be evaluated first.

You could cheat a little by insisting that your callers not pass in raw expressions such as (println "Odd! ") but instead pass them in wrapped in functions. Consider the following new definition of our unless function:

(defn unless [test then-thunk]
  (if (not test)
    (then-thunk)))

Here, then-thunk is a function that is evaluated only if the test condition isn’t true. You can rewrite exhibits-oddity? as follows:

(defn exhibits-oddity? [x]
  (unless (even? x)
    #(println "Rather odd!")))

Recall that the #() reader macro characters create an anonymous function. This function now works as expected:

user=> (exhibits-oddity? 11)
Rather odd!
nil
user=> (exhibits-oddity? 10)
nil

This solution still isn’t quite satisfactory. It forces callers to wrap the then expression inside a function. Using the #() reader macro involves just one extra character, but the language gives no warning if the caller forgets to use it. What you want is something that works similar to if, which is a special form built into Clojure. Now, let’s write a macro to solve this problem.

The Unless Macro

You know that the if form can be used to write the unless form, as long as you can avoid the evaluation of the then argument, unless it’s needed. You tried the approach of delaying evaluation using the function wrapper in the previous section, but you can do a lot better with a macro. Consider the following definition:

(defmacro unless [test then]
  (list 'if (list 'not test)
    then))

This generates an s-expression of the form (if (not test) then) when the macro is expanded. Let’s rewrite exhibits-oddity? using this macro:

(defn exhibits-oddity? [x]
  (unless (even? x)
    (println "Very odd, indeed!")))

This works as expected. The unless form is replaced by the new expression generated by the macro expansion. You can check this at the REPL using the macroexpand function:

user=>(macroexpand
        '(unless (even? x) (println "Very odd, indeed!")))
(if (not (even? x)) (println "Very odd, indeed!"))

Once this expanded form of unless replaces the unless form itself, it’s in turn evaluated to produce the right result. This final definition of unless works as expected, and the callers don’t need to know that there’s anything special about it. In fact, as far as callers are concerned, unless could have been supplied by the Clojure language itself.

 

macroexpand and macroexpand-1

macroexpand-1 is a useful function when writing macros, because it can be used to check if the transformation of s-expressions is working correctly. macroexpand-1 expands an s-expression by evaluating the macro named by the first symbol in the form. If the first symbol doesn’t name a macro, the form is returned as is.

macroexpand is a function that repeatedly calls macroexpand-1 until the first symbol of the expanded form is no longer a macro. It can be used to test cases where macros expand to forms that in turn call macros.

 

If it isn’t obvious already, you just added a feature to the Clojure language. That’s neat! What’s more, such macros are quite common. For instance, Clojure provides when, when-not, cond, if-not, and so on that are all constructs that allow conditional execution of code and are all implemented as macros. This is cool; after all, if macros are good enough to create parts of Clojure itself, then they’re good enough for your programs.

The example in this section showed you the basics of creating a control-flow macro. But the way you generated s-expressions in the previous unless macro can become unwieldy quickly. Clojure provides a more convenient way to write macros that doesn’t involve constructing lists using the list function. This approach involves generating code via templates.

7.1.3. Macro templates

Let’s consider our unless macro again. Here it is, for convenience:

(defmacro unless [test then]
  (list 'if (list 'not test)
    then))

This is a tiny macro, and the s-expression it generates is quite simple. If you wanted to generate or transform a large, nested s-expression, the repeated calls to list would become quite tedious. It would also be hard to see the structure of the s-expression being generated because the repeated occurrence of the symbol list would be in the way of reading the structure easily. Clojure provides a way out through its back-quote reader macro, which we’ll explore now.

Templating

Anyone who has programmed a web application in the last few years knows what a templating system is. It allows HTML generation from a sort of blueprint. Some parts are fixed and some are to be filled in when the template is expanded. Examples are JSP (Java Server Pages) and RHTML (Rails HTML) pages.

If generating HTML code can be made easier using templates, you can imagine the same thing would be true for generating Clojure code. This is why the macro system supports templates through the backquote (') reader macro. Let’s see it in action by rewriting the unless macro from before:

(defmacro unless [test then]
  `(if (not ~test)
     ~then))

 

Redefining macros

By the way, when you redefine a macro, you have to reevaluate any functions that use it. If you don’t, such functions will appear to use the old definition of the macro. This happens because macro expansion happens only once, and in the case of such function definitions, the expansions were from the older definition. Remember to reevaluate your functions when you change a macro used by any of them.

 

Our new macro definition certainly looks much clearer! The exact form is immediately obvious, minus a few characters: the backquote and the unquote (~). The back-quote starts the template. The template will be expanded into an s-expression and will be returned as the return value of the macro. Clojure calls the backquote the syntax quote character.

Symbols that appear inside the template are used as is when the template is expanded. In the JSP analogy, these might be fixed text that doesn’t change each time the page is rendered. Things that do need to change, say, parameters passed to the macro, are unquoted using the ~ character. Unquoting is the opposite of quoting. Because the whole template is inside a backquote (a quote), the ~ is used to undo that quotation so that values can be passed through.

Imagine if we hadn’t unquoted the then parameter in our macro definition:

(defmacro unless [test then]
  `(if (not ~test)
     then))

This would cause the symbol then to appear in the s-expression returned by this macro. That could cause Clojure to throw an error when the macro is used in a definition, complaining that it was unable to resolve the then symbol. You can see why this would happen by examining the output of the macro:

user=>(macroexpand
       '(unless (even? x) (println "Very odd, indeed!")))
(if (clojure.core/not (even? x)) user/then)

Based on this expansion, you can infer that if this macro were used, Clojure would complain that user/then is an unknown var. This is why you need to unquote anything that must be replaced with its value in the template. Next, we’ll look at another form of unquoting.

Splicing

You’ll now try to use our unless macro to do more than one thing if the test condition is satisfied. Consider the following new definition of exhibits-oddity?:

(defn exhibits-oddity? [x]
  (unless (even? x)
    (println "Odd!")
    (println "Very odd!")))

This won’t work, because unless accepts only two parameters, and you’re attempting to pass it more arguments. You can overcome this using the do form that you learned about in chapter 2:

(defn exhibits-oddity? [x]
  (unless (even? x)
    (do
      (println "Odd!")
      (println "Very odd!"))))

This works but is a bother; you have to use the do form everywhere you want more than one thing in the then part of your unless form. To make things more convenient for the callers of your macro, you can include the do form in the expansion:

(defmacro unless [test & exprs]
  `(if (not ~test)
     (do ~exprs)))

Now the unless macro accepts multiple expressions that will be executed if the test condition fails, and they’ll be enclosed inside a do form. Let’s try it with our latest exhibits-oddity? function:

user=> (exhibits-oddity? 11)
Odd!
Very odd!
; Evaluation aborted.
No message.
  [Thrown class java.lang.NullPointerException]

Hmm, that’s strange. It does print text from both calls but then aborts with an exception. The previously seen function macroexpand-1 can help you debug this situation:

user=>(macroexpand-1 '(unless (even? x)
                        (println "Odd!")
                        (println "Very odd!")))

(if (clojure.core/not (even? x))
  (do ((println "Odd!") (println "Very odd!"))))

There’s an extra pair of parentheses around the expressions you passed into the unless macro as then. The return value of println is nil, which causes the then clause to reduce to (nil nil). The extra parentheses cause this expression to be interpreted as a function call, throwing the NullPointerException that you saw earlier.

The solution is to eliminate the extra pair of parentheses. But because then is passed in as the remaining arguments to unless, it’s a list. This is where the unquote splice reader macro (~@) comes in. Instead of taking a list and unquoting it as is using the unquote (~), the unquote splicing macro splices the contents of the list into the container list. Let’s rewrite the unless macro using it:

(defmacro unless [test & exprs]
  `(if (not ~test)
     (do ~@exprs)))

With this definition of unless, our exhibits-oddity? function works just fine. This use of do that wraps the returned expressions from a macro is quite common, and it’s a convenience all the callers of your macros will appreciate.

One final aspect of writing macros that we’ll consider before moving on is that of variable capture.

Generating Names

In most Lisps, writing macros can get tricky. Well, they can get tricky in Clojure too, but the language makes things easier than other Lisps. Consider the following (incorrect) example:

(defmacro def-logged-fn [fn-name args & body]
  `(defn ~fn-name ~args
     (let [now (System/currentTimeMillis)]
       (println "[" now "] Call to" (str (var ~fn-name)))
       ~@body)))

The idea behind this macro is to create a function that logs the fact that it was called. Although Clojure allows the macro to be defined, using it throws an exception:

user=> (def-logged-fn printname [name]
        (println "hi" name))
; Evaluation aborted.
Can't let qualified name: user/now
  [Thrown class java.lang.Exception]

The problem is that the macro attempts to use a namespace-qualified name in the let binding, which is illegal. You can confirm this using macroexpand-1:

user> (macroexpand-1 '(def-logged-fn printname [name]
        (println "hi" name)))
(clojure.core/defn printname [name]
  (clojure.core/let [user/now (java.lang.System/currentTimeMillis)]
    (clojure.core/println "[" user/now ":] Call to"
       (clojure.core/str (var printname)))
    (println "hi" name)))

The let form can’t use qualified names like user/now, and that’s what Clojure is complaining about. If Clojure didn’t expand now into a namespace qualified user/now (where user is the current namespace), then now might shadow another value with the same name. This situation is illustrated here, where daily-report is a function that might run a report for a given day:

(def-logged-fn daily-report [the-day]
 ;; code to generate a report here
)

Now, let’s see what happens if we use the function in the following way:

(let [now "2009-10-22"]
  (daily-report now))

This doesn’t work as expected, because the value of now that the daily-report function sees isn’t "2009-10-22" but a number like 1259828075387. This is because the value set up in the previous let form is captured by the one in the let form generated by the macro. This behavior is known as variable capture, and it can happen in most Lisps.

To avoid this, Clojure expands the names into their fully qualified names, causing the exception you saw earlier. So how do you use the let form to introduce new names? This is where the reader macro # comes in. It generates unique names that won’t conflict with others that might be used in the code that’s passed into the macro. This facility is called auto-gensym, because it automatically generates a symbol that’s unique enough to be used as a name for things. Here’s our def-logged-fn that uses this facility:

(defmacro def-logged-fn [fn-name args & body]
  `(defn ~fn-name ~args
     (let [now# (System/currentTimeMillis)]
       (println "[" now# "] Call to" (str (var ~fn-name)))
       ~@body)))

It’s that simple. The auto-gensym uses the specified prefix when generating a name. For example, now# might expand to now__14187__auto__. Clojure will replace all occurrences of each use of auto-gensym with the same generated symbol.

This new definition of def-logged-fn will create a function that logs calls to it correctly. Redefine the previously defined printname function, and try calling it now:

user=> (printname "celeste")
[ 1259955655338 ] Call to #'user/printname
hi celeste
nil

Variable capture is a fact of life in all Lisps, and you need to avoid it when it’s undesired. Clojure makes this easier than most Lisps through this auto-gensym facility. In chapter 15, you’ll see why you might want the effect of variable capture when we explore anaphoric macros.

We’ve covered a lot of macro basics so far. Before moving on, let’s take a moment to summarize the reasons to use macros.

7.1.4. Recap—why macros?

As you saw in the previous section, macros can be more powerful than functions because they can do things functions can’t: delay (or even choose not to do) the execution of code, change the normal flow of execution, add syntactic forms, add brand-new abstractions to the language, or just make things convenient for callers. This chapter has examples of some of these uses. Macros can also move parts of computation from runtime to compile time, and you’ll see examples of this in chapter 15.

In this section, we’ll discuss the possibilities offered by a programming language that features a macro system.

Code Generation

Generating or transforming code is a rather common way of dealing with certain aspects of writing programs. Most programmers use code generation, even if they aren’t always cognizant of doing so. The most obvious example is the use of a compiler: it takes source code and generates some form of executable code, either machine language or byte code for a virtual machine. Parts of compilers are themselves often generated from descriptions of the language grammar. XSLT transforms are often used to convert one kind of structured XML document to other types of documents.

There are many other examples. API documentation is often created via an automated process that extracts annotated comments from the source code. Database access layers often generate all the SQL they need from high-level descriptions of tables or the model classes themselves. User interface toolkits often have associated programs that can generate code to create GUI layouts. Web service frameworks can generate standards-compliant interfaces from descriptions. Web application frameworks usually include template-based systems that generate HTML code.

Sometimes programs are written to explicitly generate source code files in order to handle some kind of pattern in the main application under development. For instance, in a multitier Java system, you might generate code for JavaBean classes from some other set of domain classes. Such programs often manipulate strings of text to do their job.

This kind of metaprogramming is primitive, and languages such as Ruby improve on it by providing language-level facilities to define classes and methods at runtime. Clojure provides almost an ultimate form of metaprogramming by allowing the programmer to generate or manipulate code as data.

Syntax and DSLs

We’ve already looked at how you can use macros to add syntactic forms to Clojure. When combined with bottom-up design and domain-specific abstractions, macros can transform the solution space into one or more domain-specific languages (DSLs) with which to code the application. We’ll examine examples of such a design approach in later chapters of this book.

Plain convenience

Macros can make life easy for the callers of your functions. Things like the implicit do form that you saw in the previous section are common additions to macros. In the next section, you’ll see some examples of macros. This will give you an idea of how people use them and how you might use them in your own programs.

7.2. Macros from within Clojure

In this section, we’ll look at some macros. Many come from the source code of the Clojure language itself; some are from elsewhere. These examples should give you a flavor of macro style and ideas about how to use macros in your own code.

Let’s begin our journey with examples from the Clojure language itself. As mentioned in the previous section, much of Clojure is implemented in Clojure itself, and a lot of that code is macros. This allows the core language to remain small; Clojure has only about a dozen special forms. This approach allows most other features of the language to be developed in Clojure itself. We’ll examine a few macros now.

7.2.1. comment

The comment macro is a great one to start with, because it’s so simple. Here’s the complete implementation:

(defmacro comment [& body])

The comment macro does nothing—literally. This is an example of ignoring code altogether, as opposed to changing the flow of execution or delaying it. The comment macro returns nil. The comment macro allows you to comment out parts of your program or to add comments to your code.

7.2.2. declare

Here’s a macro that does a little bit more. The declare macro accepts one or more symbols, in order to let Clojure know that there may be references to them in the code that follows. The macro goes through each argument and creates a var with that name. Typically, these vars are redefined at a later point in the program.

(defmacro declare [& names]
  `(do
     ~@(map #(list 'def %) names)))

Let’s see how it works by using the macroexpand function:

user=> (macroexpand  '(declare add multiply subtract divide))
(do
  (def add)
  (def multiply)
  (def subtract)
  (def divide))

The formatting, isn’t part of the macro expansion. This is just a simple way to get rid of duplication from having to define multiple vars. You couldn’t accomplish this with a function, by the way, because def is a special form that accepts only a symbol. Inside of macros, all special forms become available because we’re operating at the s-expression (or symbolic) level. This is an important advantage of macros.

7.2.3. defonce

We’ll now look at a macro that evaluates conditional expressions. defonce is a macro that accepts the name of a var and an initialization expression. But if the var has already been initialized once (has a root binding), it won’t be reinitialized. The implementation of this macro is straightforward, so we don’t even need to use macro expansion to see what’s going on:

(defmacro defonce [name expr]
  `(let [v# (def ~name)]
     (when-not (.hasRoot v#)
       (def ~name ~expr))))

7.2.4. and

Let’s now look at a slightly more complex example. In most languages, and (and other logical operators) are implemented as special forms. In other words, they’re built into the core of the language. In Clojure, and is just another macro:

(defmacro and
  ([] true)
  ([x] x)
  ([x & next]
   `(let [and# ~x]
      (if and# (and ~@next) and#))))

This is an elegant piece of code! When and is called with no arguments, it returns true. When called with a single argument, the return value is the argument itself (remember that anything other than nil or false is treated as true). When there are multiple arguments, the macro evaluates the first argument. It then tests it with the if form. If the value is logically true, the macro calls itself with the remaining arguments. The process then repeats. If the evaluation of any argument returns a logical false, the if form returns that value as is.

Let’s use macroexpand to see what happens:

user=>(macroexpand '(and (even? x) (> x 50) (< x 500)))
(let* [and__4357__auto__ (even? x)]
 (if and__4357__auto__
   (clojure.core/and (> x 50) (< x 500))
    and__4357__auto__))

You may see something slightly different because the auto-gensym will create different names for the local symbols. Also, remember that macroexpand doesn’t expand macros contained in subexpressions. In reality, the macro will be completely expanded, and the final expanded s-expression will replace the original call to and.

7.2.5. time

This is a rather handy macro, useful for quick checks on how slow or fast your code is running. It accepts an expression, executes it, prints the time it took to execute, and then returns the result of the evaluation. Here’s an example:

user=> (time (* 1331 13531))
"Elapsed time: 0.04 msecs"
18009761

Using the time macro isn’t as sophisticated as using a profiler, for instance, but can be quite useful for quick benchmarks of your code. Here’s how it’s implemented:

(defmacro time [expr]
  `(let [start# (. System (nanoTime))
         ret# ~expr]
     (prn
       (str "Elapsed time: "
            (/ (double (- (. System (nanoTime)) start#)) 1000000.0)
            " msecs"))
     ret#))

As you can see, the macro starts a timer before evaluating the expression passed in. The value of the expression is captured and returned after the timer is stopped and the duration printed to the console.

These are just a few macros that can be found in Clojure’s source code. As mentioned earlier, it’s advantageous for a language to have a small core and have all other features built on top of it using regular code. Clojure does this in an elegant fashion, and reading through the source code is a great way to learn the tricks of the trade. You’ll now write some macros of our own.

7.3. Writing your own macros

So far, you’ve learned the basic theory of Clojure’s macro system. You also saw some macros that form part of the Clojure language. You’ll now write a few of your own to see how you might use macros in your own programs. The first is a simple macro called infix—to help you get started. Then you’ll write one called randomly, which will appear to add a new control structure to Clojure. The next is defwebmethod, which could be the beginning of a DSL for writing web applications. The final is assert-true, which could be the beginning of a unit-testing framework for Clojure.

7.3.1. infix

In chapter 1, we talked about an infix macro, which would allow you to call mathematical operators using infix notation. Here’s how you might implement it:

(defmacro infix [expr]
  (let [[left op right] expr]
    (list op left right)))

It’s a trivial implementation: it just rearranges the function symbol and the arguments back into prefix notation. It’s also a fairly naïve implementation because it supports only two terms and doesn’t do any kind of error checking. Still, it’s a fun little macro.

7.3.2. randomly

This is an example of a control-flow macro. There are often situations where you want to randomly pick a path of execution. Such a requirement might arise, for instance, if you wanted to introduce some randomness into your code.

randomly accepts any number of s-expressions and picks one at random. Here’s the implementation:

(defmacro randomly [& exprs]
  (let [len (count exprs)
        index (rand-int len)
        conditions (map #(list '= index %) (range len))]
     `(cond ~@(interleave conditions exprs))))

rand-int is a function that returns a random integer between zero and its argument. Here you pass the length of the incoming exprs to the rand-int function. You then use nth to pick out an expression to evaluate. Let’s test it:

user=>(randomly
         (println "amit") (println "deepthi") (println "adi"))
adi
nil

Let’s try it one more time:

user=>(randomly
        (println "amit") (println "deepthi") (println "adi"))
deepthi
nil

And once more:

user=>(randomly
         (println "amit") (println "deepthi") (println "adi"))
adi
nil

The macro works as expected, evaluating only one of the three expressions. Obviously, given the randomization, your output will look different. Here’s what the macro transforms the passed in s-expressions into:

user=>(macroexpand-1 '(randomly
                        (println "amit")
                        (println "deepthi")
                        (println "adi")))
(clojure.core/cond
  (= 0 0) (println "amit")
  (= 0 1) (println "deepthi")
  (= 0 2) (println "adi"))

Again, given the randomization, your expansion may look different. Indeed, if you expand it several times, you’ll see that the condition clauses in the cond form change. Incidentally, there’s an easier way to achieve the same effect. Consider the following implementation:

(defmacro randomly-2 [& exprs]
  (nth exprs (rand-int (count exprs))))

Try it at the REPL to confirm that it works. There’s one unintended consequence of this macro, and I’ll leave figuring it out as an exercise for the reader. Hint: what happens when it’s called from within the body of a function definition?

7.3.3. defwebmethod

You’ll now write a macro that has nothing to do with changing the flow of execution of your code but is a convenience macro that makes life easier for those who use it. It will also appear to add a feature that’s specific to building web applications to Clojure.

In essence, the web is made dynamic through programs that generate different HTML documents based on certain input parameters. You can use Clojure functions for this purpose, where each function might correspond to something the user requests. For instance, you could write a function that accepts a username and a date and returns a report of that day’s expenses. The parameters of the request might be bundled in a hash map and given to each function as a request object. Each function could then query the request object for the parameters it needs, process the request as required, and then return appropriate HTML. Here’s what such a function might look like:

(defn login-user [request]
  (let [username (:username request)
        password (:password request)]
    (if (check-credentials username password)
      (str "Welcome back, " username ", " password " is correct!")
      (str "Login failed!"))))

Here, check-credentials might be a function that would look up authentication information from a database. For your purposes, let’s define it as follows:

(defn check-credentials [username password]
 true)

Also, login-user would return real HTML as opposed to the strings you’re returning. It should give a general idea about the structure of such functions, though. Let’s try it:

(def request {:username "amit" :password "123456"})

user=> (login-user request)
"Welcome back, amit, 123456 is correct!"

The trouble with this is that every function like login-user must manually query values out of the request map. The example here needs two parameters—username and password—but you can certainly imagine functions that need many more. It would be quite tedious to have to pull them out from the request map each time. Consider the following macro:

(defmacro defwebmethod [name args & exprs]
  `(defn ~name [{:keys ~args}]
     ~@exprs))

Let’s now use this macro to define a new version of login-user as follows:

(defwebmethod login-user [username password]
  (if (check-credentials username password)
    (str "Welcome, " username ", " password " is still correct!")
    (str "Login failed!")))

You can try this version of the function on the REPL:

user=> (login-user request)
"Welcome, amit, 123456 is still correct!"

For programmers who don’t know the internals of defwebmethod, it appears that it’s literally a new language abstraction, designed specifically for web applications. Any names specified in the parameters list are automatically pulled out of the request map and set up with the right value (the function defined still takes the same argument). You can specify the names of the function parameters in any order, which is a nice convenience.

You can imagine other domain-specific additions to Clojure written this way.

7.3.4. assert-true

For our last example, let’s write a macro that you can use to assert that an s-expression evaluates to true. Let’s see how you might use it:

user=> (assert-true (= (* 2 4) (/ 16 2)))
true

user=> (assert-true (< (* 2 4) (/ 18 2)))
true

You might use assert-true in a set of unit tests. You might be tempted to have multiple such assertions in a single unit test, all verifying related functionality. The trouble with having several assertions in one unit test is that when something fails, it isn’t immediately obvious what failed. Line numbers are useful, as are custom error messages that some unit-testing frameworks allow. In our little macro, we’d like to see the code that failed. It might work as follows:

user=> (assert-true (>= (* 2 4) (/ 18 2)))
(* 2 4) is not >= 9
  [Thrown class java.lang.RuntimeException]

Using literal code like this is a natural fit for macros. Here’s the macro:

(defmacro assert-true [test-expr]
  (let [[operator lhs rhs] test-expr]
    `(let [lhsv# ~lhs rhsv# ~rhs ret# ~test-expr]
       (if-not ret#
         (throw (RuntimeException.
                  (str '~lhs " is not " '~operator " " rhsv#)))
         true))))

It’s quite a straightforward implementation. A binding form is used to tease apart test-expr into its constituent operator, lhs, and rhs parts. The generated code then uses these to do their thing, best understood by looking at a sample macro expansion:

user=> (macroexpand-1 '(assert-true (>= (* 2 4) (/ 18 2))))
(clojure.core/let [lhsv__11965__auto__ (* 2 4)
                   rhsv__11966__auto__ (/ 18 2)
                   ret__11967__auto__ (>= (* 2 4) (/ 18 2))]
   (clojure.core/if-not ret__11967__auto__
     (throw (java.lang.RuntimeException.
       (clojure.core/str (quote (* 2 4))
           " is not " (quote >=) " " rhsv__11966__auto__)))
   true))

As mentioned earlier, this macro is extremely straightforward. You can improve it by adding some semantic error checking to handle some situations where invalid expressions are passed. Consider the following definition:

(defmacro assert-true [test-expr]
  (if-not (= 3 (count test-expr))
    (throw (RuntimeException.
         "Argument must be of the form
               (operator test-expr expected-expr)")))
  (if-not (some #{(first test-expr)} '(< > <= >= = not=))
    (throw (RuntimeException.
       "operator must be one of < > <= >= = not=")))
  (let [[operator lhs rhs] test-expr]
    `(let [lhsv# ~lhs rhsv# ~rhs ret# ~test-expr]
       (if-not ret#
         (throw (RuntimeException.
            (str '~lhs " is not " '~operator " " rhsv#)))
         true))))

This works for the two situations where someone passes a malformed expression into the macro:

user=> (assert-true (>= (* 2 4) (/ 18 2) (+ 2 5)))
; Evaluation aborted.
Argument must be of the form (operator test-expr expected-expr)
  [Thrown class java.lang.RuntimeException]

It also checks for the case where someone tries to use an operator that isn’t supported:

user=> (assert-true (<> (* 2 4) (/ 16 2)))
; Evaluation aborted.
operator must be one of < > <= >= = not=
  [Thrown class java.lang.RuntimeException]

This example shows how macros can make it easy to perform domain-specific semantic checking of not just values but of the code itself. In other languages, this might have required some serious parsing. Clojure’s code-as-data approach pays off in this scenario.

7.4. Summary

We said in the beginning of this chapter that macros distinguish Clojure (and other Lisps) from most other programming languages. Macros allow the programmer to add new language features to Clojure. Indeed, you can build whole new layers of functionality on top of Clojure, which makes it appear that an entire new language has been created. An example of this is Clojure’s concurrency system: it isn’t part of the language per se; it’s implemented as a set of Java classes and associated Clojure functions and macros.

Macros truly blur the distinction between the language designer and the application programmer, allowing you to add to the language as you see fit. For instance, should you feel that Clojure lacks a construct that would allow you to express something, you don’t have to wait for the next version of the language or wish you were using a different language. You can add that feature yourself.

We’ve covered most of the basics of Clojure the language, and we’re ready to start exploring some real-world applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.228