Monday, July 10, 2006

a lesson in CLOS-based interface fine-tuning

I recently made a fine-tuning pass through the argument lists for event-handling functions in Graphic-Forms and had an interesting experience.

First a bit of background. In Graphic-Forms, applications handle each event type by implementing the associated generic function. E.g., one implements EVENT-SELECT to handle a button press or menu item click. Prior to this past weekend, every event function included a time argument, which was a system-generated timestamp for the event. Other UI libraries have included that information (such as SWT's TypedEvent, which has a time instance variable) so I had figured it would be a good idea to include it in my library. Going ahead with this design choice without thinking harder was my first mistake: events in SWT are objects that are passed to interface methods, thus they can carry any amount of ancillary information with little added cost (from the standpoint of an application implementing those interface methods).

Instead of realizing the design error, I made the narrower observation that an event timestamp is needed in only a few special circumstances -- and in all of the test code I have written so far, I was writing a (declare (ignore time)) form everywhere. That gave me some empirical evidence to justify changing the API. I wanted to alleviate the need for application code to worry about that argument, yet still have access to it in the rare cases where it is needed.

So, my first inclination was to make the time argument &optional. oops! In CLOS, optional arguments in methods have to be congruent in their number just like required arguments, so event method implementors are not saved any work this way.

Then I considered making time a keyword argument, so method implementors could specify &allow-other-keys when they don't need the timestamp. But that adds more clutter than it saves; in other words, a symptom of this being the wrong reason to use keyword arguments.

Finally, the light bulb above my head flickered on, and I decided to remove the time argument completely. As a substitute, I decided to provide a separate access function. Problem solved.

I took away a couple conclusions from this. First, CLOS not only has powerful features, but there are easy-to-see signs that you're misusing them if you just pay attention. And more importantly, it's a mistake to copy a feature without thinking hard.


Anonymous said...

CLOS is beautifully designed. But like lisp, only expert user can use it properly.

Coming from a C++/Java bg, the biggest thing for me to overcome is how to name the accessor function properly.

With Java, you can make your accessor function name very generic, like

But with CLOS, such a vague name will make the code very confusing to read (foo (bar (name (get-object x)))

I have seen people using convention like class.verb as accessor function (like in UCW) but it's confusing in another way (repeated noun). (e.g. (session.timestamp session)

I've yet come up with a systematic approach to naming CLOS accessor.

Jack Unrue said...

I agree. I tend to prefer a convention of appending "-of" to accessors. So in your example, that would be "name-of". Unfortunately, I find I can't be 100% consistent with that, because sometimes there is an existing generic function whose name I want to keep using.

Oh well :-)