From: Brian Denheyer
Subject: what exactly does a metaclass do for you?
Date: 
Message-ID: <m2n2ifjbd9.fsf@soggy.aqua.com>
I'm trying to figure out what the significance of :metaclass is in clos.

The particular example which has me very confused is from 3.1 of Art
of the MOP.

Since :metaclass is used quite a bit through the book I need to get it
figured out before continuing.

In the example they create a counted class (this is actually stklos
syntax) like so:

	(define-class counted-class () 
	  ((counter :initform 0)))

	(define-class counted-rectangle (rectangle)
	  ((:metaclass counted-class)))

So I gotta know, what's up with that ?

Why wouldn't you just use a mix-in ?

	(define-class counted-rectangle (counted-class rectangle))


If some could supply a nice explanation of the why's and wherefore's
of :metaclass and metaclasses, I'd apprecitate it.

Thanks

	

-- 

Brian Denheyer
······@northwest.com

From: Kent M Pitman
Subject: Re: what exactly does a metaclass do for you?
Date: 
Message-ID: <sfwafef0y3u.fsf@world.std.com>
Brian Denheyer <······@northwest.com> writes:

> I'm trying to figure out what the significance of :metaclass is in clos.

I'm convinced the counted-class example is a bad illustration of metaclass
exactly because there are solutions which don't require the use of
metaclass.

IMO, the best way to understand the true REASON for a metaclass in the
first place is to accomodate multiple representations.  For example,
to understand why a NUMBER can be an object even though it has no
"slots" per se.  It is possible to explain this without metaclasses,
but it's simply easier to say that the choice of representation is a
property of the meta-class.

But there are also metaclasses that differ in things like the policy for
structure.  For example, STRUCTURE-CLASS and STANDARD-CLASS don't look that
different from a layout standpoint, but the accessors for each differ 
radically because STRUCTURE-CLASS assumes only single-inheritance and
STANDARD-CLASS assumes multiple inheritance.  Why does that matter? Well,
in single-inheritance, if you know FOO's A slot is in position 3, then
you know it will be in position 3 for all subclasses of FOO, and so for
any structure-class, the accessor compiles to pretty much
 (structure-ref obj 3)
or some other known fixnum.  Very efficient.  In CLOS, you can't know
what he position of slot A is because multiple inheritance might move it
around.  So you reference the object indirectly through a "mapping table"
which means an extra memory reference finding the slot number N from the
mapping table and then 
 (structure-ref obj N)
from the computed value of N.

Once you start to list the various policies that classes could have, you
realize that some can be done at the "class level" and some at the 
program level.  It's cleaner to do counted-class at the class level,
but it's not the only way to do it.

e.g., to do counted-class as a mixin, one must do:

 (defvar *class-count-table* (make-hash-table))

 (defclass counted-class-mixin () ())

 (defmethod initialize-instance :before ((me counted-class-mixin)
					 &rest options)
   (declare (ignore options))
   (incf (gethash (class-of me) *class-count-table* 0)))

Note the use of external storage because you REALLY want to store this
information in the class itself, but classes don't have such a slot,
so you have to extend the storage by simulating an extra slot through
an outboard hash table.

So by playing with the metaclass of the class, you can affect what's
in the class itself and therefore arrange to have a slot that counts
it per-slot.

Note this is similar to :allocation :class for a slot except that CL
made a design error (fixed in Dylan) of having only :allocation :class
and not :allocation :each-subclass.  You'd think you could just write:

 (defclass bad-counted-class-mixin () 
   ((count :initarg  :count
           :initform 0
           :allocation :class)))

and then everything would be cool, but this in fact shares the count
cell among bad-counted-class-mixin and all its subclasses (i.e.,
anything it's mixed into), so that there is only one thing counted.
If I make a counted FOO class and a counted BAR class and instantiate
2 FOO's and 3 BAR's, the COUNT for both FOO and BAR will be 5 because
it's sharing the cell owend by the COUNT class.  One really wants
:allocation :each-subclass, but it's just not there.  So that's why I
had to use (class-of me) as a hash table key in the code I wrote
above.  And it's why allocating a new metaclass is neater and cleaner
(no hash table).

Does this help?
From: Rainer Joswig
Subject: Re: what exactly does a metaclass do for you?
Date: 
Message-ID: <348939E1.544AF1E@lavielle.com>
Brian Denheyer wrote:

> I'm trying to figure out what the significance of :metaclass is in clos.
>
> The particular example which has me very confused is from 3.1 of Art
> of the MOP.
>
> Since :metaclass is used quite a bit through the book I need to get it
> figured out before continuing.
>
> In the example they create a counted class (this is actually stklos
> syntax) like so:
>
>         (define-class counted-class ()
>           ((counter :initform 0)))
>
>         (define-class counted-rectangle (rectangle)
>           ((:metaclass counted-class)))
>
> So I gotta know, what's up with that ?
>
> Why wouldn't you just use a mix-in ?
>
>         (define-class counted-rectangle (counted-class rectangle))
>
> If some could supply a nice explanation of the why's and wherefore's
> of :metaclass and metaclasses, I'd apprecitate it.

CLOS makes classes first class objects. Classes are objects and you have the
full power of CLOS to modify its own behaviour. In most other
language you either don't have classes as objects or you can't change
the metaclass of a class per class. That is, there is usually no :metaclass option
for defining classes in other languages and this information is not
available at runtime.

In this example "person" is a class, "counted-class" is a metaclass
and "hugo" is a person. This is like the example of AMOP page 72.

So if we define a class counted-class:SQL 33 > (defclass counted-class (standard-class) ((n :initform 0)))
#<STANDARD-CLASS COUNTED-CLASS 203AC014>

It inherits from standard-class, which is a common metaclass in CLOS
(others were structure-class, builtin-class, ...). counted-class has
a superclass: standard-class. It also has a metaclass (the class of of counted-class):
standard-class. Interestingly the superclass and the metaclass are the
same.

SQL 34 > (defclass person () ((name :initarg :name)) (:metaclass counted-class))
#<COUNTED-CLASS PERSON 203A7AE4>

Person now has no special superclass (just the usual, we haven't specified
anything else). It has not the usual metaclass (which would be standard-class). The
metaclass is counted-class. So the class person  is represented by
an object and the class of this object is COUNTED-CLASS.

[some of my lossage deleted]

SQL 37 > (defmethod make-instance :after ((class counted-class) &key) (incf (slot-value class 'n)))
#<STANDARD-METHOD MAKE-INSTANCE (:AFTER) (COUNTED-CLASS) 21DDBC4C>

Now we are defining at the metaclass level (!!!) what happens when
an instance of some class with the metaclass counted-class
is being created. We are saying that after the usual
make-instance protocol has run (which is usually inherited
from standard-class), we wish to increment the slot "N" of the
class object (not the instance object we are creating!!).

SQL 38 > (make-instance 'person :name "hugo")
#<PERSON 203D688C>

This object has no slot "N". Just the object of the class  "person"
has the slot "N". We are wishing to see the usual application level -
person objects and what you can do with them in isolation.
The metaclass level where we are counting the instances
has nothing to do with the implementation of "person".

Let us look at the object which represents the class person.

SQL 39 > (describe (find-class 'person))

#<COUNTED-CLASS PERSON 203A7AE4> is a COUNTED-CLASS
N                            1
NAME                         PERSON
...

So what does it show? The metaclass mechanism allows you to put
the counter  information to where it belongs: at the class level.
Mixing this information into the object (by making person inherit
from a counter class and changing the initialize-instance protocol)
is a kludge for languages without a meta object protocol.
Once you understand that
the informations about the program (the classes, the methods,
the slot descriptions, ...) are objects and you have access to
them at runtime (!!), you see that you have a new level
of programmability. Since the counter example can be done
in different ways, you can compare the different implementation
versions quite nicely. The usual version mixes the counting-instances protocol
into the class/object levels, where it does not belong. Using
slots with (sub)class allocation would then be a usual way.
But it from a modelling point of view it really belongs
into the class itself and the metaobject protocol allows
you to express that with CLOS cleanly. This comes really handy
when you have more complicated problems.

You can see how people are working around this in
C++ by reading the various patterns books.

Greetings,

Rainer