Good ! Do you consider putting this on the clojure wikibook ?
Cheers, -- Laurent 2009/6/8 Meikel Brandmeyer <m...@kotka.de>: > 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 > > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---