Dear Clojurians,

in the past there were several discussions on the list
regarding the one or the other macro. Please let me
show some design principles, which I believe are good
ideas for macro design.

On the occasion of a question in another thread we will
write a small helper macro to add an action listener to
a Swing component.

Let's start with the code we want to produce.

(.addActionListener component
  (proxy [ActionListener] []
    (actionPerformed [evt] (do-something-with evt) (do-more-stuff))))

This should be easy to translate into a macro.

(defmacro add-action-listener
  [component & body]
  `(.addActionListener ~component
     (proxy [ActionListener] []
       (~'actionPerformed [~'evt] ~...@body))))

Note the ugly actionPerformed quoting. The invocation
looks like:

(add-action-listener component
  (do-something-with evt)
  (do-more-stuff))

Note that we had to capture "evt" to get access to
the event, which is passed to the handling code. This
means we get into trouble, when we want to access an
"external" evt in our handling code. This is ugly.

So we specify the name to use explicitly.

(defmacro add-action-listener
  [component evt & body]
  `(.addActionListener ~component
     (proxy [ActionListener] []
       (~'actionPerformed [~evt] ~...@body))))

Note the slightly different quoting of evt. Again the
invocation:

(add-action-listener component the-evt
  (do-something-with the-evt)
  (do-more-stuff))

Hurray. We have a working macro. Time for refactoring.
We should ask ourselves: "Why do we actually need a macro?"
Macro writing is hard and one should keep the macros as
small as possible if not avoiding them at all.

Of course one might argue: "Yadda yadda. That's just a
style question." So here is another motivation.

Suppose we have some-handler-fn which we want to us in
our action listener. Using our macro this would look
like this:

(add-action-listener component the-evt
  (some-handler-fn the-evt))

Bleh. Now this is ugly and verbose. We have to think
about a name for a thing which we just immediately pass
on to a function. Why should we bother with such details?
So let's write a function for that.

(defn add-action-listener*
  [component handler]
  (.addActionListener component
    (proxy [ActionListener] []
      (actionPerformed [evt] (handler evt)))))

Now the invocation looks like this:

(add-action-listener* component some-handler-fn)

Much more concise and to the spot. And note: in the
function we didn't have to mess around with strange
quotations. We just chose whatever name we liked for
the event argument without danger of interference.

But wait. Now we implemented the same functionality
twice. We should DRY this moisture.

As we saw, we can easily achieve the desired effect
if we have a handler function. So why not base the
macro on the function?

(defmacro add-action-listener
  [component evt & body]
  `(add-action-listener* ~component (fn [~evt] ~...@body)))

Note the trick: we created a function which takes
the given event name as argument and has body as
it's body. So the event parameter is available in
the body under the given name. The anonymous fn
is then simply passed to the star function.

"Ah. More yadda yadda. I don't have handler fns!"

So why would it still be a good idea to have the
macro use some driver function?

Suppose you are hunting a bug. Your suspicion is
that there is some trouble with the event handling.
So you want to log some message about the event.
You start changing the macro.

(defmacro add-action-listener
  [component evt & body]
  `(.addActionListener ~component
     (proxy [ActionListener] []
       (~'actionPerformed [~evt] (println "Got event:" ~evt) ~...@body))))

And now you can track the event passing. Eh.. well..
Not quite! First you have to recompile all your code
and restart the application. Since a macro gets
expanded at compile time, there is no way to change
the expansion afterwards.

Consider the same change to the function version.
Now all you have to do is to just reload the single
function in the running application. This can be
easily done by environments like SLIME or VimClojure.
Et voila. The next time the function is called you already
get the benefit. Without recompilation of the whole app...

So here the complete macro-function pair + invocation:

(defn add-action-listener*
  [component handler]
  (.addActionListener component
    (proxy [ActionListener] []
      (actionPerformed [evt] (handler evt)))))

(defmacro add-action-listener
  [component evt & body]
  `(add-action-listener* ~component (fn [~evt] ~...@body)))

(add-action-listener component the-evt
  (do-something-with the-evt)
  (do-more-stuff))

(add-action-listener* component some-handler-fn)

So to summarise:

- write the code which you want in your macro
- write a macro which expands into the code with
  variable parts as macro arguments.
- extract the parts which are not required to be
  a macro as a function
- modify the macro to simply call the function

Surprisingly many macros can be handled like this.

I hope this helps you to write more robust and
flexible macros.

Sincerely
Meikel


Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to