To CHANGE-CLASS or not to CHANGE-CLASS

(This article isn't about clipping paths or graphics or Vecto, it's about CLOS, protocols, and change-class.)

Clipping paths in in Vecto have these semantics:

  • the initial clipping path covers the whole canvas, so it has no effect on drawing
  • the clipping path can never get bigger (updates always intersect with the current clipping path), but
  • the clipping path is saved and restored by WITH-GRAPHICS-STATE, so you can localize changes

Vecto implements clipping with a grayscale mask channel. But I wanted a couple optimizations:

  • no clipping path creation or drawing overhead for the initial, empty clipping path
  • no data copying overhead in WITH-GRAPHICS-STATE unless the clipping path is actually modified (i.e. copy-on-write)

The protocol to support this has the generic functions EMPTYP, COPY, CLIPPING-DATA, and WRITABLE-CLIPPING-DATA. A more efficient drawing function is used if the canvas's clipping path object is EMPTYP, otherwise a slower function that references CLIPPING-DATA is used. WITH-GRAPHICS-STATE copies the clipping path with COPY. Clipping path updates are written to the channel returned by WRITABLE-CLIPPING-DATA.

I initially implemented this with three classes: CLIPPING-PATH, EMPTY-CLIPPING-PATH, and PROXY-CLIPPING-PATH. Here's how each class handles each generic function:

EMPTY-CLIPPING-PATH CLIPPING-PATH PROXY-CLIPPING-PATH
EMPTYP returns true returns false
COPY returns new EMPTY-CLIPPING-PATH returns new PROXY-CLIPPING-PATH,
data slot is EQ to original
CLIPPING-DATA no method returns data slot value
WRITABLE-CLIPPING-DATA change-class to CLIPPING-PATH,
initializes and returns data
returns data slot change-class to CLIPPING-PATH,
set data slot to copy-seq of data slot

Then I read Joe Marshall's take on CHANGE-CLASS, which he calls — with some caveats — horrendous. I don't want to use horrendous things! So I implemented a different approach using only one CLIPPING-PATH class:

  • add a COPY-ON-WRITE-P slot, initially false
  • a CLIPPING-PATH is EMPTYP if its data slot is unbound
  • COPY creates a new CLIPPING-PATH instance
    • if the original is EMPTYP, the new instance is also empty
    • otherwise it has the same (EQ) data as the original and a copy-on-write flag of t.
  • CLIPPING-DATA returns the data slot
  • WRITABLE-CLIPPING-DATA checks if the clipping path is empty:
    • if so, initializes and returns the data slot
    • otherwise, checks the copy-on-write flag:
      • if set, COPY-SEQs the data slot to itself, returns the new data
      • otherwise it returns the data directly

The second approach seems ok to me (and I don't need a table to explain it), but the first approach doesn't seem that horrendous either. I didn't choose either technique for any special reasons; they're just the first things that occurred to me.

What do you think? Are they about the same, or does one seem better to you? Is there some other approach that would be even nicer? Let me know.

Tags:

Comments

(Anonymous)

CHANGE-CLASS is fine. ;)

...just keep the caveats about CHANGE-CLASS in the HyperSpec in mind.

Pascal

(Anonymous)

It isn't <em>*that*</em> horrendous.

The situation you describe in this post is one where change-class can be put to good use. You've got an abstract class (a clipping-path) and three concrete representation classes. You are using change-class to transparently switch representations as needed.

Horrendous is more along the lines of this:

(let ((my-fish (get-fish *aquarium*)))
  (change-class my-fish 'bicycle)
  (shift-gears my-fish 3))

You can really screw up the semantics of object identity if you use change-class in an undisciplined way, but you can also use it to preserve object identity in the face of representation changes, schema upgrades, and incremental debugging.

(Anonymous)

Oops.

I accidentally hit submit. Pretend the html directives aren't in the subject and that I signed the message.

~Joe Marshall

Change Class

I recently had a similar dilemma parsing elf files.

Once I'd read all the sections of the file, I had an array of elf-section objects. Some of the sections have quite complicated semantics (eg relocation entries referencing symbols, symbol names, and some other code/data section) that couldn't really be accounted for while the sections were being read.

So I had two options - do a second pass over the sections, chaging them to a more specialised type than elf-section and have specific methods specialise on these for symbol lookup, or string lookup or whatever, or just have a number of different methods for retreiving different things specialised only to the base section type.

I went with this but I'm beginning to think it was the wrong decision in terms of long-term extensibility - it would be better to have the sections specialise themselves as there are many possible types: ultimately it's a means of reducing the combinatorial explosion becaue the section-entry acessors specialise only on what they have to and not on the general case and validate they are used correctly.


I tried it last night. I'm working on a system which uses URNs. I wanted a clean abstraction, both so other URN applications could be built on this, and also so I don't wind up asking myself if I've dealt with the URN specification while working underneath it.

It occurred to me that the namespacing system of the URN is a great candidate for having some kind of dispatch function. So when you parse a URN, you get back a generiic URN if there's no handler for that namespace, but if there is, you get a special subclass back.

Initially I was just going to have it create and return a new instance of the subclass after creating the superclass, but then I found your blog entry and thought I'd try it out with CHANGE-CLASS. So far, I think it's quite nice. I could even write some magical stuff that would just try and locate the class by the namespace identifier and issue the change class, and I wouldn't even have to have a dispatch table of identifier -> functions.

It seems appropriate to me in this case (and in your case) to use CHANGE-CLASS because we're converting between similar things (actually just casting down the inheritance hierarchy.) Based on what little I know, I could definitely make a case for CHANGE-CLASS being bad if one were going to change between unrelated classes a lot, but for these border situations it doesn't seem like a bad architecture to me at all.

Has your opinion of it changed at all in the last year?
I think it's cool, but I haven't used it again since then.

July 2014

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
Powered by LiveJournal.com