Bill Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

April 2004
Sun Mon Tue Wed Thu Fri Sat
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30
Mar  May

Modifying Object behavior in CL

Monday, April 12, 2004

On comp.lang.lisp, a poster relates (based on comments from an interview with the creator of Ruby) how it is possible to create a proxy in Ruby that will allow you to wrap an object and modify it's behavior. He asks how it is possible to do the equivalent in CL. Pascal Costanza replies with a brief explanation of a variety of different ways this could be done in CL:

As usual, CL provides a range of options. Here's the text of the discussion:
Ralph Richard Cook wrote:

>>Yukihiro Matsumoto: One example is a proxy. Instead of designing individual
>>proxy classes for each particular class, in Ruby you can create an all
>>purpose proxy that can wrap any object. The proxy can probe the object
>>inside of it and just morph into the proxy for that object. The proxy can
>>add methods to itself so it has the same interface as the wrapped object,
>>and each of those methods can delegate to the corresponding method in the
>>wrapped object. So an all-purpose proxy, which can be used to wrap any
>>object, is an example of how a library class can adapt to the environment.
>
> I thought this was really cool, to have a proxy that can be handed an
> arbitrary object, have the proxy probe the object to find out what
> messages/methods it responds to, and create a wrapper object for just
> that object, and not a class of objects.
>
> How would this be done in Common Lisp? Would you have to make a custom
> class & methods for each object, and if that is the case, how would
> the custom classes get garbage collected when the instance is done?


It's not clear to me what he is exactly talking about. AFAIK, a proxy object is an object that is handed around in place of another one, and takes care of instantiating that other object as soon as is it is needed. A wrapper is an object that intercepts messages to a wrapped object in order to add more behavior.

It depends on what you actually need in order to implement these things in Common Lisp. An important point is that Common Lisp does not revolve around the notion of objects, as is usually the case in languages like Smalltalk, Java and Ruby, but rather around the concept of generic functions. Functions do not belong to objects, or their classes, but rather functions operate on objects. So if you want to wrap functions with additional behavior, you would just add before/after/around methods to the functions that you want to wrap.

If you really would like to add behavior to a whole class, you would use mixins - i.e. classes that are specifically designed to provide types that additional before/after/around methods can refer to.

If you want to add behavior to a single object, you can use eql specializers.

If you want to be more generic and want to wrap all functions that operate on a certain class of objects, or on certain objects, you need to use the metaobject protocol (MOP) that, although not part of the ANSI standard, is part of most CLOS implementations, with some variations.

On the one hand, the MOP provides introspective capabilities. That is, you can determine all the superclasses and subclasses of a class, and as a next step, determine all the generic functions that specialize in one or more parameters on the so determined classes. Then, you can define additional before/after/around methods on the so determined generic functions, with eql specializers if necessary.

On the other hand, the MOP also provides intercession. This is achieved via metaclasses - each class is itself an object that is an instance of a metaclass. All the CLOS classes are by default instances of standard-class, but you can derive your own subclass of standard-class, for example proxy-standard-class. Then you can use that new metaclass to implement new behavior for objects of classes that are instances of proxy-standard-class. In this context, it is important to note that slot (field) accesses go through generic functions. [1] For proxy objects, it is sufficient to wrap just the slot accesses - all other generic functions can be left unchanged. (They have to use the slots at some stage - otherwise there would not be a need for proxies.)

This means that you can add methods to slot-value-using-class and implement the proxy behavior there. (For example, you can use change-class to replace the proxy object with the real one.)

Sometimes, you don't need the full capabilities of the MOP, but can live with a combination of mixins and modified slot accesses. In such cases, you don't need the MOP at all, because CLOS already provides a restricted way to modify slot accesses via the generic function slot-missing.

Note that it is not easy to achieve a truly super-general way to wrap all objects that might ever come up. However, it is very unlikely that you really want that. For example, Common Lisp also still provides a large number of functions that are not generic, because there are situations in which genericity is counterproductive. Sooner or later, you want to control in more detail where you want to allow, and where you want to forbid, genericity anyway.

This is a very rough overview what is possible in CLOS + MOP. As I said, the quoted passage above is too fuzzy to give more specific hints. Otherwise, this would turn out in a complete tutorial. If you want more information, I recommend the following texts:

- Andreas Paepcke, "User-level language crafting" - at his website
- Gregor Kiczales et al., "The Art of the Metaobject Protocol" (book)
- http://www.lisp.org/mop


[1] There are some provisions in the MOP specification that ensure that this doesn't imply a general performance penalty.

emacs Copyright © 2004 by Bill Clementson