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
smime.p7s
Description: S/MIME cryptographic signature