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
-~----------~----~----~----~------~----~------~--~---

Reply via email to